github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/api/query_builder.go (about)

     1  package api
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/ungtb10d/cli/v2/pkg/set"
     8  )
     9  
    10  func squeeze(r rune) rune {
    11  	switch r {
    12  	case '\n', '\t':
    13  		return -1
    14  	default:
    15  		return r
    16  	}
    17  }
    18  
    19  func shortenQuery(q string) string {
    20  	return strings.Map(squeeze, q)
    21  }
    22  
    23  var issueComments = shortenQuery(`
    24  	comments(first: 100) {
    25  		nodes {
    26  			id,
    27  			author{login},
    28  			authorAssociation,
    29  			body,
    30  			createdAt,
    31  			includesCreatedEdit,
    32  			isMinimized,
    33  			minimizedReason,
    34  			reactionGroups{content,users{totalCount}},
    35  			url,
    36  			viewerDidAuthor
    37  		},
    38  		pageInfo{hasNextPage,endCursor},
    39  		totalCount
    40  	}
    41  `)
    42  
    43  var issueCommentLast = shortenQuery(`
    44  	comments(last: 1) {
    45  		nodes {
    46  			author{login},
    47  			authorAssociation,
    48  			body,
    49  			createdAt,
    50  			includesCreatedEdit,
    51  			isMinimized,
    52  			minimizedReason,
    53  			reactionGroups{content,users{totalCount}}
    54  		},
    55  		totalCount
    56  	}
    57  `)
    58  
    59  var prReviewRequests = shortenQuery(`
    60  	reviewRequests(first: 100) {
    61  		nodes {
    62  			requestedReviewer {
    63  				__typename,
    64  				...on User{login},
    65  				...on Team{
    66  					organization{login}
    67  					name,
    68  					slug
    69  				}
    70  			}
    71  		}
    72  	}
    73  `)
    74  
    75  var prReviews = shortenQuery(`
    76  	reviews(first: 100) {
    77  		nodes {
    78  			author{login},
    79  			authorAssociation,
    80  			submittedAt,
    81  			body,
    82  			state,
    83  			reactionGroups{content,users{totalCount}}
    84  		}
    85  		pageInfo{hasNextPage,endCursor}
    86  		totalCount
    87  	}
    88  `)
    89  
    90  var prLatestReviews = shortenQuery(`
    91  	latestReviews(first: 100) {
    92  		nodes {
    93  			author{login},
    94  			authorAssociation,
    95  			submittedAt,
    96  			body,
    97  			state
    98  		}
    99  	}
   100  `)
   101  
   102  var prFiles = shortenQuery(`
   103  	files(first: 100) {
   104  		nodes {
   105  			additions,
   106  			deletions,
   107  			path
   108  		}
   109  	}
   110  `)
   111  
   112  var prCommits = shortenQuery(`
   113  	commits(first: 100) {
   114  		nodes {
   115  			commit {
   116  				authors(first:100) {
   117  					nodes {
   118  						name,
   119  						email,
   120  						user{id,login}
   121  					}
   122  				},
   123  				messageHeadline,
   124  				messageBody,
   125  				oid,
   126  				committedDate,
   127  				authoredDate
   128  			}
   129  		}
   130  	}
   131  `)
   132  
   133  func StatusCheckRollupGraphQL(after string) string {
   134  	var afterClause string
   135  	if after != "" {
   136  		afterClause = ",after:" + after
   137  	}
   138  	return fmt.Sprintf(shortenQuery(`
   139  	statusCheckRollup: commits(last: 1) {
   140  		nodes {
   141  			commit {
   142  				statusCheckRollup {
   143  					contexts(first:100%s) {
   144  						nodes {
   145  							__typename
   146  							...on StatusContext {
   147  								context,
   148  								state,
   149  								targetUrl,
   150  								createdAt
   151  							},
   152  							...on CheckRun {
   153  								name,
   154  								checkSuite{workflowRun{workflow{name}}},
   155  								status,
   156  								conclusion,
   157  								startedAt,
   158  								completedAt,
   159  								detailsUrl
   160  							}
   161  						},
   162  						pageInfo{hasNextPage,endCursor}
   163  					}
   164  				}
   165  			}
   166  		}
   167  	}`), afterClause)
   168  }
   169  
   170  func RequiredStatusCheckRollupGraphQL(prID, after string) string {
   171  	var afterClause string
   172  	if after != "" {
   173  		afterClause = ",after:" + after
   174  	}
   175  	return fmt.Sprintf(shortenQuery(`
   176  	statusCheckRollup: commits(last: 1) {
   177  		nodes {
   178  			commit {
   179  				statusCheckRollup {
   180  					contexts(first:100%[1]s) {
   181  						nodes {
   182  							__typename
   183  							...on StatusContext {
   184  								context,
   185  								state,
   186  								targetUrl,
   187  								createdAt,
   188  								isRequired(pullRequestId: %[2]s)
   189  							},
   190  							...on CheckRun {
   191  								name,
   192  								checkSuite{workflowRun{workflow{name}}},
   193  								status,
   194  								conclusion,
   195  								startedAt,
   196  								completedAt,
   197  								detailsUrl,
   198  								isRequired(pullRequestId: %[2]s)
   199  							}
   200  						},
   201  						pageInfo{hasNextPage,endCursor}
   202  					}
   203  				}
   204  			}
   205  		}
   206  	}`), afterClause, prID)
   207  }
   208  
   209  var IssueFields = []string{
   210  	"assignees",
   211  	"author",
   212  	"body",
   213  	"closed",
   214  	"comments",
   215  	"createdAt",
   216  	"closedAt",
   217  	"id",
   218  	"labels",
   219  	"milestone",
   220  	"number",
   221  	"projectCards",
   222  	"reactionGroups",
   223  	"state",
   224  	"title",
   225  	"updatedAt",
   226  	"url",
   227  }
   228  
   229  var PullRequestFields = append(IssueFields,
   230  	"additions",
   231  	"baseRefName",
   232  	"changedFiles",
   233  	"commits",
   234  	"deletions",
   235  	"files",
   236  	"headRefName",
   237  	"headRefOid",
   238  	"headRepository",
   239  	"headRepositoryOwner",
   240  	"isCrossRepository",
   241  	"isDraft",
   242  	"latestReviews",
   243  	"maintainerCanModify",
   244  	"mergeable",
   245  	"mergeCommit",
   246  	"mergedAt",
   247  	"mergedBy",
   248  	"mergeStateStatus",
   249  	"potentialMergeCommit",
   250  	"reviewDecision",
   251  	"reviewRequests",
   252  	"reviews",
   253  	"statusCheckRollup",
   254  )
   255  
   256  // IssueGraphQL constructs a GraphQL query fragment for a set of issue fields.
   257  func IssueGraphQL(fields []string) string {
   258  	var q []string
   259  	for _, field := range fields {
   260  		switch field {
   261  		case "author":
   262  			q = append(q, `author{login}`)
   263  		case "mergedBy":
   264  			q = append(q, `mergedBy{login}`)
   265  		case "headRepositoryOwner":
   266  			q = append(q, `headRepositoryOwner{id,login,...on User{name}}`)
   267  		case "headRepository":
   268  			q = append(q, `headRepository{id,name}`)
   269  		case "assignees":
   270  			q = append(q, `assignees(first:100){nodes{id,login,name},totalCount}`)
   271  		case "labels":
   272  			q = append(q, `labels(first:100){nodes{id,name,description,color},totalCount}`)
   273  		case "projectCards":
   274  			q = append(q, `projectCards(first:100){nodes{project{name}column{name}},totalCount}`)
   275  		case "milestone":
   276  			q = append(q, `milestone{number,title,description,dueOn}`)
   277  		case "reactionGroups":
   278  			q = append(q, `reactionGroups{content,users{totalCount}}`)
   279  		case "mergeCommit":
   280  			q = append(q, `mergeCommit{oid}`)
   281  		case "potentialMergeCommit":
   282  			q = append(q, `potentialMergeCommit{oid}`)
   283  		case "comments":
   284  			q = append(q, issueComments)
   285  		case "lastComment": // pseudo-field
   286  			q = append(q, issueCommentLast)
   287  		case "reviewRequests":
   288  			q = append(q, prReviewRequests)
   289  		case "reviews":
   290  			q = append(q, prReviews)
   291  		case "latestReviews":
   292  			q = append(q, prLatestReviews)
   293  		case "files":
   294  			q = append(q, prFiles)
   295  		case "commits":
   296  			q = append(q, prCommits)
   297  		case "lastCommit": // pseudo-field
   298  			q = append(q, `commits(last:1){nodes{commit{oid}}}`)
   299  		case "commitsCount": // pseudo-field
   300  			q = append(q, `commits{totalCount}`)
   301  		case "requiresStrictStatusChecks": // pseudo-field
   302  			q = append(q, `baseRef{branchProtectionRule{requiresStrictStatusChecks}}`)
   303  		case "statusCheckRollup":
   304  			q = append(q, StatusCheckRollupGraphQL(""))
   305  		default:
   306  			q = append(q, field)
   307  		}
   308  	}
   309  	return strings.Join(q, ",")
   310  }
   311  
   312  // PullRequestGraphQL constructs a GraphQL query fragment for a set of pull request fields.
   313  // It will try to sanitize the fields to just those available on pull request.
   314  func PullRequestGraphQL(fields []string) string {
   315  	invalidFields := []string{"isPinned", "stateReason"}
   316  	s := set.NewStringSet()
   317  	s.AddValues(fields)
   318  	s.RemoveValues(invalidFields)
   319  	return IssueGraphQL(s.ToSlice())
   320  }
   321  
   322  var RepositoryFields = []string{
   323  	"id",
   324  	"name",
   325  	"nameWithOwner",
   326  	"owner",
   327  	"parent",
   328  	"templateRepository",
   329  	"description",
   330  	"homepageUrl",
   331  	"openGraphImageUrl",
   332  	"usesCustomOpenGraphImage",
   333  	"url",
   334  	"sshUrl",
   335  	"mirrorUrl",
   336  	"securityPolicyUrl",
   337  
   338  	"createdAt",
   339  	"pushedAt",
   340  	"updatedAt",
   341  
   342  	"isBlankIssuesEnabled",
   343  	"isSecurityPolicyEnabled",
   344  	"hasIssuesEnabled",
   345  	"hasProjectsEnabled",
   346  	"hasWikiEnabled",
   347  	"mergeCommitAllowed",
   348  	"squashMergeAllowed",
   349  	"rebaseMergeAllowed",
   350  
   351  	"forkCount",
   352  	"stargazerCount",
   353  	"watchers",
   354  	"issues",
   355  	"pullRequests",
   356  
   357  	"codeOfConduct",
   358  	"contactLinks",
   359  	"defaultBranchRef",
   360  	"deleteBranchOnMerge",
   361  	"diskUsage",
   362  	"fundingLinks",
   363  	"isArchived",
   364  	"isEmpty",
   365  	"isFork",
   366  	"isInOrganization",
   367  	"isMirror",
   368  	"isPrivate",
   369  	"isTemplate",
   370  	"isUserConfigurationRepository",
   371  	"licenseInfo",
   372  	"viewerCanAdminister",
   373  	"viewerDefaultCommitEmail",
   374  	"viewerDefaultMergeMethod",
   375  	"viewerHasStarred",
   376  	"viewerPermission",
   377  	"viewerPossibleCommitEmails",
   378  	"viewerSubscription",
   379  
   380  	"repositoryTopics",
   381  	"primaryLanguage",
   382  	"languages",
   383  	"issueTemplates",
   384  	"pullRequestTemplates",
   385  	"labels",
   386  	"milestones",
   387  	"latestRelease",
   388  
   389  	"assignableUsers",
   390  	"mentionableUsers",
   391  	"projects",
   392  
   393  	// "branchProtectionRules", // too complex to expose
   394  	// "collaborators", // does it make sense to expose without affiliation filter?
   395  }
   396  
   397  func RepositoryGraphQL(fields []string) string {
   398  	var q []string
   399  	for _, field := range fields {
   400  		switch field {
   401  		case "codeOfConduct":
   402  			q = append(q, "codeOfConduct{key,name,url}")
   403  		case "contactLinks":
   404  			q = append(q, "contactLinks{about,name,url}")
   405  		case "fundingLinks":
   406  			q = append(q, "fundingLinks{platform,url}")
   407  		case "licenseInfo":
   408  			q = append(q, "licenseInfo{key,name,nickname}")
   409  		case "owner":
   410  			q = append(q, "owner{id,login}")
   411  		case "parent":
   412  			q = append(q, "parent{id,name,owner{id,login}}")
   413  		case "templateRepository":
   414  			q = append(q, "templateRepository{id,name,owner{id,login}}")
   415  		case "repositoryTopics":
   416  			q = append(q, "repositoryTopics(first:100){nodes{topic{name}}}")
   417  		case "issueTemplates":
   418  			q = append(q, "issueTemplates{name,title,body,about}")
   419  		case "pullRequestTemplates":
   420  			q = append(q, "pullRequestTemplates{body,filename}")
   421  		case "labels":
   422  			q = append(q, "labels(first:100){nodes{id,color,name,description}}")
   423  		case "languages":
   424  			q = append(q, "languages(first:100){edges{size,node{name}}}")
   425  		case "primaryLanguage":
   426  			q = append(q, "primaryLanguage{name}")
   427  		case "latestRelease":
   428  			q = append(q, "latestRelease{publishedAt,tagName,name,url}")
   429  		case "milestones":
   430  			q = append(q, "milestones(first:100,states:OPEN){nodes{number,title,description,dueOn}}")
   431  		case "assignableUsers":
   432  			q = append(q, "assignableUsers(first:100){nodes{id,login,name}}")
   433  		case "mentionableUsers":
   434  			q = append(q, "mentionableUsers(first:100){nodes{id,login,name}}")
   435  		case "projects":
   436  			q = append(q, "projects(first:100,states:OPEN){nodes{id,name,number,body,resourcePath}}")
   437  		case "watchers":
   438  			q = append(q, "watchers{totalCount}")
   439  		case "issues":
   440  			q = append(q, "issues(states:OPEN){totalCount}")
   441  		case "pullRequests":
   442  			q = append(q, "pullRequests(states:OPEN){totalCount}")
   443  		case "defaultBranchRef":
   444  			q = append(q, "defaultBranchRef{name}")
   445  		default:
   446  			q = append(q, field)
   447  		}
   448  	}
   449  	return strings.Join(q, ",")
   450  }