Skip to content

Commit

Permalink
move stats logic from command to library and update output options (#527
Browse files Browse the repository at this point in the history
)
  • Loading branch information
briandowns authored Jan 13, 2025
1 parent 50bd9bb commit dee6c6e
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 113 deletions.
136 changes: 23 additions & 113 deletions cmd/release/cmd/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ package cmd

import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"sort"
"strings"
"time"

"github.com/briandowns/spinner"
"github.com/google/go-github/v39/github"
"github.com/rancher/ecm-distro-tools/release"
"github.com/rancher/ecm-distro-tools/repository"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)

var (
repo *string
startDate *string
endDate *string
format *string
)

var repoToOwner = map[string]string{
Expand All @@ -27,17 +28,6 @@ var repoToOwner = map[string]string{
"k3s": "k3s-io",
}

type monthly struct {
count int
captains []string
tags []string
}

type relStats struct {
count int
monthly map[time.Month]monthly
}

// statsCmd represents the stats command
var statsCmd = &cobra.Command{
Use: "stats",
Expand All @@ -58,128 +48,48 @@ var statsCmd = &cobra.Command{
return errors.New("end date before start date")
}

githubToken := os.Getenv("GITHUB_TOKEN")

ctx := context.Background()
client := repository.NewGithub(ctx, githubToken)
client := repository.NewGithub(ctx, rootConfig.Auth.GithubToken)

s := spinner.New(spinner.CharSets[31], 100*time.Millisecond)
s.HideCursor = true
s.Writer = os.Stderr
s.Start()

var total int
data := make(map[int]relStats)
captains := make(map[string]int)
sd, err := release.Stats(ctx, client, from, to, repoToOwner[*repo], *repo)
if err != nil {
return err
}

lo := github.ListOptions{
PerPage: 100,
var b []byte

switch *format {
case "json":
b, err = json.Marshal(sd)
case "yaml":
b, err = yaml.Marshal(sd)
default:
return errors.New("unrecognized format")
}
for {
releases, resp, err := client.Repositories.ListReleases(ctx, repoToOwner[*repo], *repo, &lo)
if err != nil {
return err
}

for _, release := range releases {
releaseDate := release.GetCreatedAt().Time
if releaseDate.After(from) && (releaseDate.Before(to) || releaseDate.Equal(to)) {
total++

if _, ok := data[int(release.CreatedAt.Year())]; !ok {
data[int(release.CreatedAt.Year())] = relStats{
count: 1,
monthly: map[time.Month]monthly{
release.CreatedAt.Month(): {
count: 1,
captains: []string{
*release.Author.Login,
},
tags: []string{
*release.Name,
},
},
},
}
continue
}

rs := data[int(release.CreatedAt.Year())]
rs.count++

mon := rs.monthly[release.CreatedAt.Month()]
mon.count++
mon.captains = append(mon.captains, *release.Author.Login)
mon.tags = append(mon.tags, *release.Name)

rs.monthly[release.CreatedAt.Month()] = mon

data[int(release.CreatedAt.Year())] = rs

if release.Author.Login != nil {
if _, ok := captains[*release.Author.Login]; !ok {
captains[*release.Author.Login]++
continue
}
captains[*release.Author.Login]++
}
}
}

if resp.NextPage == 0 {
break
}
lo.Page = resp.NextPage
if err != nil {
return err
}

s.Stop()

for year := range data {
fmt.Printf("\n%d:\n", year)

months := make([]int, 0, len(data[year].monthly))
for k := range data[year].monthly {
months = append(months, int(k))
}
sort.Ints(months)

for _, m := range months {
mon := time.Month(m)
tmp := data[year].monthly[mon]
tmp.captains = dedup(tmp.captains)
data[year].monthly[mon] = tmp
captains := strings.Join(data[year].monthly[mon].captains, ", ")
tags := strings.Join(data[year].monthly[mon].tags, ", ")
fmt.Printf(" %-9s\n Count: %3d\n Captains: %s\n Tags: %s\n",
mon, data[year].monthly[mon].count, captains, tags)
}
}

fmt.Printf("\nTotal: %d\n", total)
fmt.Println(string(b))

return nil
},
}

func dedup(slice []string) []string {
seen := make(map[string]struct{})
result := []string{}

for _, val := range slice {
if _, ok := seen[val]; !ok {
seen[val] = struct{}{}
result = append(result, val)
}
}

return result
}

func init() {
rootCmd.AddCommand(statsCmd)

repo = statsCmd.Flags().StringP("repo", "r", "", "repository")
startDate = statsCmd.Flags().StringP("start", "s", "", "start date")
endDate = statsCmd.Flags().StringP("end", "e", "", "end date")
format = statsCmd.Flags().StringP("format", "f", "json", "format (json|yaml)")

if err := statsCmd.MarkFlagRequired("repo"); err != nil {
fmt.Println(err.Error())
Expand Down
126 changes: 126 additions & 0 deletions release/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,132 @@ func LatestPreRelease(ctx context.Context, client *github.Client, owner, repo, v
return latestRelease(versions), nil
}

// StatsMonthly
type StatsMonthly struct {
Count int
Captains []string
Tags []string
}

// RelStats
type RelStats struct {
Count int
Monthly map[time.Month]StatsMonthly
}

// StatsData
type StatsData struct {
Total int64 `json:"total"`
Data map[int]RelStats `json:"data"`
Captains map[string]int `json:"captains"`
}

// dedup creates and returns a new slices based on the given slice
// but with duplicate entries removed.
func dedup(slice []string) []string {
seen := make(map[string]struct{})
result := []string{}

for _, val := range slice {
if _, ok := seen[val]; !ok {
seen[val] = struct{}{}
result = append(result, val)
}
}

return result
}

// Stats collects and processes information regarding a set of releases for the given repo
// over the given period of time.
func Stats(ctx context.Context, client *github.Client, startDate, endDate time.Time, owner, repo string) (*StatsData, error) {
if endDate.Before(startDate) {
return nil, errors.New("end date before start date")
}

sd := StatsData{
Data: make(map[int]RelStats),
Captains: make(map[string]int),
}

lo := github.ListOptions{
PerPage: 100,
}
for {
releases, resp, err := client.Repositories.ListReleases(ctx, owner, repo, &lo)
if err != nil {
return nil, err
}

for _, release := range releases {
releaseDate := release.GetCreatedAt().Time
if releaseDate.After(startDate) && (releaseDate.Before(endDate) || releaseDate.Equal(endDate)) {
sd.Total++

if _, ok := sd.Data[int(release.CreatedAt.Year())]; !ok {
sd.Data[int(release.CreatedAt.Year())] = RelStats{
Count: 1,
Monthly: map[time.Month]StatsMonthly{
release.CreatedAt.Month(): {
Count: 1,
Captains: []string{
*release.Author.Login,
},
Tags: []string{
*release.Name,
},
},
},
}
continue
}

rs := sd.Data[int(release.CreatedAt.Year())]
rs.Count++

mon := rs.Monthly[release.CreatedAt.Month()]
mon.Count++
mon.Captains = append(mon.Captains, *release.Author.Login)
mon.Tags = append(mon.Tags, *release.Name)

rs.Monthly[release.CreatedAt.Month()] = mon

sd.Data[int(release.CreatedAt.Year())] = rs

if release.Author.Login != nil {
if _, ok := sd.Captains[*release.Author.Login]; !ok {
sd.Captains[*release.Author.Login]++
continue
}
sd.Captains[*release.Author.Login]++
}
}
}

if resp.NextPage == 0 {
break
}
lo.Page = resp.NextPage
}

for year := range sd.Data {
months := make([]int, 0, len(sd.Data[year].Monthly))
for k := range sd.Data[year].Monthly {
months = append(months, int(k))
}
sort.Ints(months)

for _, m := range months {
mon := time.Month(m)
tmp := sd.Data[year].Monthly[mon]
tmp.Captains = dedup(tmp.Captains)
sd.Data[year].Monthly[mon] = tmp
}
}

return &sd, nil
}

func latestRelease(versions []*github.RepositoryRelease) *string {
sort.Slice(versions, func(i, j int) bool {
return versions[i].PublishedAt.Before(versions[j].PublishedAt.Time)
Expand Down

0 comments on commit dee6c6e

Please sign in to comment.