diff --git a/go.mod b/go.mod index 76bbf18..daaa69f 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ require ( github.com/joho/godotenv v1.5.1 github.com/pelletier/go-toml v1.9.5 github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 - go.opentelemetry.io/otel v1.33.0 - go.opentelemetry.io/otel/trace v1.33.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 + go.opentelemetry.io/otel v1.34.0 + go.opentelemetry.io/otel/trace v1.34.0 golang.org/x/mod v0.22.0 golang.org/x/oauth2 v0.25.0 golang.org/x/sync v0.10.0 @@ -21,6 +21,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel/metric v1.33.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect golang.org/x/net v0.34.0 // indirect ) diff --git a/go.sum b/go.sum index 8be8a43..5623567 100644 --- a/go.sum +++ b/go.sum @@ -25,14 +25,14 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= -go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= -go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= -go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= -go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= -go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= diff --git a/main.go b/main.go index 0a5002c..a695206 100644 --- a/main.go +++ b/main.go @@ -73,6 +73,9 @@ func main() { allForks, _ := clientGQL.GetAllForksHistory(ctx, "ceccopierangiolieugenio/pyTermTk", nil) fmt.Println(time.Time(allForks[len(allForks)-1].Day)) + allContributors, _ := clientGQL.GetNewContributorsHistory(ctx, "temporalio/temporal", nil) + fmt.Println(time.Time(allContributors[len(allContributors)-1].Day)) + // result, _ = clientGQL.GetAllStats(ctx, "kubewarden/kubewarden-controller") fmt.Println(result) diff --git a/repostats/gqlstats.go b/repostats/gqlstats.go index c80dcf3..72ace84 100644 --- a/repostats/gqlstats.go +++ b/repostats/gqlstats.go @@ -1389,3 +1389,116 @@ func (c *ClientGQL) GetAllCommitsHistory(ctx context.Context, ghRepo string, upd } return result, string(defaultBranchName), nil } + +func (c *ClientGQL) GetNewContributorsHistory(ctx context.Context, ghRepo string, updateChannel chan<- int) ([]stats.NewContributorsPerDay, error) { + repoSplit := strings.Split(ghRepo, "/") + + if len(repoSplit) != 2 || !strings.Contains(ghRepo, "/") { + return nil, fmt.Errorf("Repo should be provided as owner/name") + } + + defer func() { + if updateChannel != nil { + close(updateChannel) + } + }() + + owner := repoSplit[0] + name := repoSplit[1] + + result := []stats.NewContributorsPerDay{} + counter := &Counter{} + + _, repoCreationDate, err := c.GetTotalStars(ctx, ghRepo) + if err != nil { + log.Printf("%v\n", err) + return result, err + } + + currentTime := time.Now().UTC().Truncate(24 * time.Hour) + repoCreationDate = repoCreationDate.Truncate(24 * time.Hour) + diff := currentTime.Sub(repoCreationDate) + days := int(diff.Hours()/24 + 1) + + for i := 0; i < days; i++ { + result = append(result, stats.NewContributorsPerDay{Day: stats.JSONDay(repoCreationDate.AddDate(0, 0, i).Truncate(24 * time.Hour))}) + } + + variablesContributors := map[string]any{ + "owner": githubv4.String(owner), + "name": githubv4.String(name), + "prsCursor": (*githubv4.String)(nil), + } + + type prs struct { + State string + MergedAt time.Time + Author struct { + Login string + } + } + + var queryContributors struct { + Repository struct { + PullRequests struct { + Nodes []prs + PageInfo struct { + EndCursor githubv4.String + HasNextPage bool + } + } `graphql:"pullRequests(first: 100, orderBy: {field: CREATED_AT, direction: ASC}, after: $prsCursor)"` + } `graphql:"repository(owner: $owner, name: $name)"` + } + + seenContributors := map[string]bool{} + + for { + err := c.query(ctx, &queryContributors, variablesContributors) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + res := queryContributors.Repository.PullRequests.Nodes + + if len(res) == 0 { + break + } + + for _, pr := range res { + if pr.State == "MERGED" { + daysMerged := pr.MergedAt.Sub(repoCreationDate).Hours() / 24 + if daysMerged < 0 { + continue + } + + if _, exists := seenContributors[pr.Author.Login]; !exists { + seenContributors[pr.Author.Login] = true + result[int(daysMerged)].NewContributors++ + } + } + } + + if !queryContributors.Repository.PullRequests.PageInfo.HasNextPage { + break + } + + variablesContributors["prsCursor"] = githubv4.NewString(queryContributors.Repository.PullRequests.PageInfo.EndCursor) + + counter.Increment() + + if updateChannel != nil { + updateChannel <- counter.Value() + } + } + + for i, day := range result { + if i > 0 { + result[i].TotalNewContributors = result[i-1].TotalNewContributors + day.NewContributors + } else { + result[i].TotalNewContributors = day.NewContributors + } + } + + return result, nil +} diff --git a/stats/stats.go b/stats/stats.go index 3a9f545..217972c 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -58,6 +58,13 @@ type PRsPerDay struct { TotalCurrentlyOpen int } +// NewContributorsPerDay holds statistics about new contributors for a specific day. +type NewContributorsPerDay struct { + Day JSONDay + NewContributors int + TotalNewContributors int +} + func (t StarsPerDay) MarshalJSON() ([]byte, error) { return json.Marshal([]any{t.Day, t.Stars, t.TotalStars}) } @@ -78,6 +85,10 @@ func (t PRsPerDay) MarshalJSON() ([]byte, error) { return json.Marshal([]any{t.Day, t.Opened, t.Merged, t.Closed, t.TotalOpened, t.TotalMerged, t.TotalClosed, t.CurrentlyOpen, t.TotalCurrentlyOpen}) } +func (t NewContributorsPerDay) MarshalJSON() ([]byte, error) { + return json.Marshal([]any{t.Day, t.NewContributors, t.TotalNewContributors}) +} + type StarsHistory struct { AddedLast24H int AddedLast7d int