Skip to content

Commit

Permalink
add user activity tool with GraphQL
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Hiller <[email protected]>
  • Loading branch information
dhiller committed Dec 2, 2024
1 parent 012befe commit 294d78b
Show file tree
Hide file tree
Showing 4 changed files with 391 additions and 0 deletions.
84 changes: 84 additions & 0 deletions generators/cmd/contributions/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* This file is part of the KubeVirt project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright the KubeVirt Authors.
*
*/

package main

import (
"context"
"flag"
"fmt"
"github.com/shurcooL/githubv4"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"gopkg.in/yaml.v3"
"os"
"strings"
)

type options struct {
org string
repo string
username string
githubTokenPath string
}

func gatherOptions() (options, error) {
o := options{}
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
fs.StringVar(&o.org, "org", "kubevirt", "org name")
fs.StringVar(&o.repo, "repo", "", "repo name")
fs.StringVar(&o.username, "username", "", "github handle")
fs.StringVar(&o.githubTokenPath, "github-token", "/etc/github/oauth", "path to github token to use")
err := fs.Parse(os.Args[1:])
return o, err
}

func init() {
log.SetFormatter(&log.JSONFormatter{})
log.SetLevel(log.DebugLevel)
}

func main() {
opts, err := gatherOptions()
if err != nil {
log.Fatalf("error parsing arguments %v: %v", os.Args[1:], err)
}

token, err := os.ReadFile(opts.githubTokenPath)
if err != nil {
log.Fatalf("failed to use github token path %s: %v", opts.githubTokenPath, err)
}
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: strings.TrimSpace(string(token))},
)
httpClient := oauth2.NewClient(context.Background(), src)
graphqlClient := githubv4.NewClient(httpClient)

activity, err := generateUserActivity(graphqlClient, opts.org, opts.repo, opts.username)

if err != nil {
log.Fatalf("failed to query: %v", err)
}

out, err := yaml.Marshal(activity)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))
}
298 changes: 298 additions & 0 deletions generators/cmd/contributions/user-activity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
/*
* This file is part of the KubeVirt project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright the KubeVirt Authors.
*
*/

package main

import (
"context"
"fmt"
"github.com/shurcooL/githubv4"
"time"
)

type UserActivityResult struct {
IssuesCreated IssuesCreated
IssuesCommented IssuesCommented
PullRequestsCreated PullRequestsCreated
PullRequestsReviewed PullRequestsReviewed
PullRequestsCommented PullRequestsCommented
CommitsByUser CommitsByUser
}

type Repository struct {
Name string
}
type Author struct {
Login string
}
type IssueFragment struct {
Number int
Title string
Repository Repository
Author Author
CreatedAt time.Time
}

type IssuesCreatedNodeItem struct {
Issue IssueFragment `graphql:"... on Issue"`
}

type IssuesCreated struct {
IssueCount int
Nodes []IssuesCreatedNodeItem
}

type CommentAuthor struct {
Login string
}
type CommentItem struct {
Author CommentAuthor
CreatedAt time.Time
}

type Comments struct {
Nodes []CommentItem
}

type IssueWithCommentFragment struct {
Number int
Title string
Repository Repository
Author Author
Comments Comments `graphql:"comments(first:100, orderBy: {field: UPDATED_AT, direction: ASC} )"`
}

type IssuesCommentedNodeItem struct {
Issue IssueWithCommentFragment `graphql:"... on Issue"`
}

type IssuesCommented struct {
IssueCount int
Nodes []IssuesCommentedNodeItem
}

type PullRequestAuthor struct {
Login string
}

type PullRequestFragment struct {
Number int
Title string
CreatedAt time.Time
Author PullRequestAuthor
}

type PullRequestNodeItem struct {
PullRequest PullRequestFragment `graphql:"... on PullRequest"`
}

type PullRequestsCreated struct {
IssueCount int
Nodes []PullRequestNodeItem
}

type PullRequestReviewItem struct {
State string
}

type PullRequestReviews struct {
TotalCount int
Nodes []PullRequestReviewItem
}

type PullRequestReviewFragment struct {
Title string
Number int
CreatedAt time.Time
Reviews PullRequestReviews `graphql:"reviews(first:5, author: $username)"`
}

type PullRequestReviewNodeItem struct {
PullRequestReview PullRequestReviewFragment `graphql:"... on PullRequest"`
}

type PullRequestsReviewed struct {
IssueCount int
Nodes []PullRequestReviewNodeItem
}

type PullRequestCommentAuthor struct {
Login string
}

type PullRequestComment struct {
Author PullRequestCommentAuthor
CreatedAt time.Time
}
type PullRequestCommentsItem struct {
Nodes []PullRequestComment
}

type PullRequestCommentedRepository struct {
Name string
}

type PullRequestCommentedAuthor struct {
Login string
}

type PullRequestCommentedFragment struct {
Number int
Title string
Repository PullRequestCommentedRepository
Author PullRequestCommentedAuthor
Comments PullRequestCommentsItem `graphql:"comments(first:100, orderBy: {field: UPDATED_AT, direction: ASC} )"`
}

type PullRequestCommentedItem struct {
PullRequest PullRequestCommentedFragment `graphql:"... on PullRequest"`
}

type PullRequestsCommented struct {
IssueCount int
Nodes []PullRequestCommentedItem
}

/*
defaultBranchRef {
target {
... on Commit {
history(author: {emails: "[email protected]"}, since: "2023-11-27T00:00:00Z") {
totalCount
nodes {
commitUrl
associatedPullRequests(first: 5) {
nodes {
number
title
}
}
}
}
}
}
}
*/

type AssociatedPullRequest struct {
Number int
Title string
URL string
}

type AssociatedPullRequests struct {
Nodes []AssociatedPullRequest
}

type CommitsByUserTargetHistoryNode struct {
CommitUrl string
AssociatedPullRequests `graphql:"associatedPullRequests(first: 5)"`
}

type CommitsByUserTargetHistory struct {
TotalCount int
Nodes []CommitsByUserTargetHistoryNode
}

type CommitsByUserTargetFragment struct {
History CommitsByUserTargetHistory `graphql:"history(author: {emails: [$userEMail]}, since: $startFrom)"`
}

type CommitsByUserTargetItem struct {
Fragment CommitsByUserTargetFragment `graphql:"... on Commit"`
}

type CommitsByUserRef struct {
Target CommitsByUserTargetItem
}

type CommitsByUser struct {
DefaultBranchRef CommitsByUserRef
}

func generateUserActivity(client *githubv4.Client, org, repo, username string) (*UserActivityResult, error) {

var query struct {
IssuesCreated IssuesCreated `graphql:"issuesCreated: search(first: 5, type: ISSUE, query: $authorSearchQuery)"`
IssuesCommented IssuesCommented `graphql:"issuesCommented: search(first: 5, type: ISSUE, query: $commenterSearchQuery)"`
PullRequestsCreated PullRequestsCreated `graphql:"prsCreated: search(type: ISSUE, first: 5, query: $pullRequestsCreatedQuery)"`
PullRequestsReviewed PullRequestsReviewed `graphql:"prsReviewed: search(type: ISSUE, first: 5, query: $pullRequestsReviewedQuery)"`
PullRequestsCommented PullRequestsCommented `graphql:"prsCommented: search(last: 100, type: ISSUE, query: $pullRequestsCommentedQuery)"`
CommitsByUser CommitsByUser `graphql:"commitsByUser: repository(owner: $org, name: $repo)"`
}

oneYearAgo := time.Now().AddDate(-1, 0, 0)
fromDate := oneYearAgo.Format("2006-01-02")

variables := map[string]interface{}{
"org": githubv4.String(org),
"repo": githubv4.String(repo),
"username": githubv4.String(username),
"userEMail": githubv4.String("[email protected]"),
"startFrom": githubv4.GitTimestamp{Time: oneYearAgo},
"authorSearchQuery": githubv4.String(fmt.Sprintf(
"repo:%s/%s author:%s is:issue created:>=%s",
org,
repo,
username,
fromDate,
)),
"commenterSearchQuery": githubv4.String(fmt.Sprintf(
"repo:%s/%s commenter:%s is:issue created:>=%s",
org,
repo,
username,
fromDate,
)),
"pullRequestsCreatedQuery": githubv4.String(fmt.Sprintf(
"repo:%s/%s author:%s is:pr created:>=%s",
org,
repo,
username,
fromDate,
)),
"pullRequestsReviewedQuery": githubv4.String(fmt.Sprintf(
"repo:%s/%s reviewed-by:%s is:pr updated:>=%s",
org,
repo,
username,
fromDate,
)),
"pullRequestsCommentedQuery": githubv4.String(fmt.Sprintf(
"repo:%s/%s commenter:%s is:pr updated:>=%s",
org,
repo,
username,
fromDate,
)),
}

err := client.Query(context.Background(), &query, variables)
if err != nil {
return nil, fmt.Errorf("failed to use github query %+v with variables %v: %w", query, variables, err)
}
return &UserActivityResult{
IssuesCreated: query.IssuesCreated,
IssuesCommented: query.IssuesCommented,
PullRequestsCreated: query.PullRequestsCreated,
PullRequestsReviewed: query.PullRequestsReviewed,
PullRequestsCommented: query.PullRequestsCommented,
CommitsByUser: query.CommitsByUser,
}, nil
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ go 1.22
require gopkg.in/yaml.v3 v3.0.1

require (
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 // indirect
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
)
Loading

0 comments on commit 294d78b

Please sign in to comment.