Skip to content

Commit

Permalink
feat: provide repository insights
Browse files Browse the repository at this point in the history
  • Loading branch information
cecobask committed Sep 15, 2023
1 parent 0cf4473 commit 3aae4ff
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 18 deletions.
57 changes: 42 additions & 15 deletions cmd/insights/contributors.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
"github.com/spf13/cobra"
)

type Options struct {
type contributorsOptions struct {
// APIClient is the http client for making calls to the open-sauced api
APIClient *client.APIClient

Expand All @@ -33,9 +33,9 @@ type Options struct {

// NewContributorsCommand returns a new cobra command for 'pizza insights contributors'
func NewContributorsCommand() *cobra.Command {
opts := &Options{}
opts := &contributorsOptions{}
cmd := &cobra.Command{
Use: "contributors [flags]",
Use: "contributors url... [flags]",
Short: "Gather insights about contributors of indexed git repositories",
Long: "Gather insights about contributors of indexed git repositories. This command will show new, recent, alumni, repeat contributors for each git repository",
Args: func(cmd *cobra.Command, args []string) error {
Expand All @@ -49,15 +49,15 @@ func NewContributorsCommand() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
endpointURL, _ := cmd.Flags().GetString(constants.FlagNameEndpoint)
opts.APIClient = api.NewGoClient(endpointURL)
return run(opts)
return opts.run(context.TODO())
},
}
cmd.Flags().StringVarP(&opts.FilePath, constants.FlagNameFile, "f", "", "Path to yaml file containing an array of git repository urls")
cmd.Flags().Int32VarP(&opts.Period, constants.FlagNamePeriod, "p", 30, "Number of days, used for query filtering")
return cmd
}

func run(opts *Options) error {
func (opts *contributorsOptions) run(ctx context.Context) error {
repositories, err := utils.HandleRepositoryValues(opts.Repos, opts.FilePath)
if err != nil {
return err
Expand All @@ -71,7 +71,7 @@ func run(opts *Options) error {
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
repoContributorsInsights, err := findAllRepositoryContributorsInsights(context.TODO(), opts, repoURL)
repoContributorsInsights, err := findAllRepositoryContributorsInsights(ctx, opts, repoURL)
if err != nil {
errorChan <- err
return
Expand All @@ -89,7 +89,7 @@ func run(opts *Options) error {
}

type repositoryContributorsInsights struct {
RepoID int32
RepoID int
RepoURL string
New []string
Recent []string
Expand All @@ -102,15 +102,31 @@ func (rci *repositoryContributorsInsights) RenderTable() {
return
}
rows := []table.Row{
{"New contributors", strconv.Itoa(len(rci.New))},
{"Recent contributors", strconv.Itoa(len(rci.Recent))},
{"Alumni contributors", strconv.Itoa(len(rci.Alumni))},
{"Repeat contributors", strconv.Itoa(len(rci.Repeat))},
{
"Repository ID",
strconv.Itoa(len(strconv.Itoa(rci.RepoID))),
},
{
"New contributors",
strconv.Itoa(len(rci.New)),
},
{
"Recent contributors",
strconv.Itoa(len(rci.Recent)),
},
{
"Alumni contributors",
strconv.Itoa(len(rci.Alumni)),
},
{
"Repeat contributors",
strconv.Itoa(len(rci.Repeat)),
},
}
columns := []table.Column{
{
Title: "Repository URL",
Width: 20,
Width: getMaxTableRowWidth(rows),
},
{
Title: rci.RepoURL,
Expand All @@ -129,6 +145,17 @@ func (rci *repositoryContributorsInsights) RenderTable() {
fmt.Println(repoTable.View())
}

func getMaxTableRowWidth(rows []table.Row) int {
var maxRowWidth int
for i := range rows {
rowWidth := len(rows[i][0])
if rowWidth > maxRowWidth {
maxRowWidth = rowWidth
}
}
return maxRowWidth
}

func findRepositoryByOwnerAndRepoName(ctx context.Context, apiClient *client.APIClient, repoURL string) (*client.DbRepo, error) {
owner, repoName, err := utils.GetOwnerAndRepoFromURL(repoURL)
if err != nil {
Expand All @@ -146,7 +173,7 @@ func findRepositoryByOwnerAndRepoName(ctx context.Context, apiClient *client.API
return repo, nil
}

func findAllRepositoryContributorsInsights(ctx context.Context, opts *Options, repoURL string) (*repositoryContributorsInsights, error) {
func findAllRepositoryContributorsInsights(ctx context.Context, opts *contributorsOptions, repoURL string) (*repositoryContributorsInsights, error) {
repo, err := findRepositoryByOwnerAndRepoName(ctx, opts.APIClient, repoURL)
if err != nil {
return nil, fmt.Errorf("could not get contributors insights for repository %s: %w", repoURL, err)
Expand All @@ -155,8 +182,8 @@ func findAllRepositoryContributorsInsights(ctx context.Context, opts *Options, r
return nil, nil
}
repoContributorsInsights := &repositoryContributorsInsights{
RepoID: repo.Id,
RepoURL: repoURL,
RepoID: int(repo.Id),
RepoURL: repo.SvnUrl,
}
var (
waitGroup = new(sync.WaitGroup)
Expand Down
1 change: 1 addition & 0 deletions cmd/insights/insights.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ func NewInsightsCommand() *cobra.Command {
},
}
cmd.AddCommand(NewContributorsCommand())
cmd.AddCommand(NewRepoCommand())
return cmd
}
155 changes: 155 additions & 0 deletions cmd/insights/repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package insights

import (
"context"
"fmt"
"slices"
"strconv"

"github.com/charmbracelet/bubbles/table"
"github.com/charmbracelet/lipgloss"
"github.com/open-sauced/go-api/client"
"github.com/open-sauced/pizza-cli/pkg/api"
"github.com/open-sauced/pizza-cli/pkg/constants"
"github.com/spf13/cobra"
)

type repoOptions struct {
// APIClient is the http client for making calls to the open-sauced api
APIClient *client.APIClient

// Repo is a git repository url
Repo string

// Period is the number of days, used for query filtering
// Constrained to either 30 or 60
Period int32
}

// NewRepoCommand returns a new cobra command for 'pizza insights repo'
func NewRepoCommand() *cobra.Command {
opts := &repoOptions{}
cmd := &cobra.Command{
Use: "repo url [flags]",
Short: "Gather insights about an indexed git repository",
Long: "Gather insights about an indexed git repository. This command will show info about contributors, pull requests, etc.",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("must provide exactly one git repository url")
}
opts.Repo = args[0]
period, _ := cmd.Flags().GetInt32(constants.FlagNamePeriod)
allowedPeriods := []int32{30, 60}
if !slices.Contains(allowedPeriods, period) {
return fmt.Errorf("%s flag must be one of %v", constants.FlagNamePeriod, allowedPeriods)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
endpointURL, _ := cmd.Flags().GetString(constants.FlagNameEndpoint)
opts.APIClient = api.NewGoClient(endpointURL)
return opts.run(context.TODO())
},
}
cmd.Flags().Int32VarP(&opts.Period, constants.FlagNamePeriod, "p", 30, "Number of days, used for query filtering")
return cmd
}

type repositoryInsights struct {
RepoID int `json:"repo_id"`
RepoURL string `json:"repo_url"`
AllPullRequests int `json:"all_pull_requests"`
AcceptedPullRequests int `json:"accepted_pull_requests"`
SpamPullRequests int `json:"spam_pull_requests"`
}

func (opts *repoOptions) run(ctx context.Context) error {
repo, err := findRepositoryByOwnerAndRepoName(ctx, opts.APIClient, opts.Repo)
if err != nil {
return err
}
if repo == nil {
return nil
}
repoInsights := &repositoryInsights{
RepoID: int(repo.Id),
RepoURL: repo.SvnUrl,
}
pullRequestInsights, err := getPullRequestInsights(ctx, opts.APIClient, repo.Id, opts.Period)
if err != nil {
return err
}
repoInsights.AllPullRequests = int(pullRequestInsights.AllPrs)
repoInsights.AcceptedPullRequests = int(pullRequestInsights.AcceptedPrs)
repoInsights.SpamPullRequests = int(pullRequestInsights.SpamPrs)
repoInsights.RenderTable()
return nil
}

func (ri *repositoryInsights) RenderTable() {
if ri == nil {
return
}
rows := []table.Row{
{
"Repository ID",
strconv.Itoa(ri.RepoID),
},
{
"All pull requests",
strconv.Itoa(ri.AllPullRequests),
},
{
"Accepted pull requests",
strconv.Itoa(ri.AcceptedPullRequests),
},
{
"Spam pull requests",
strconv.Itoa(ri.SpamPullRequests),
},
}
var maxRowWidth int
for i := range rows {
rowWidth := len(rows[i][0])
if rowWidth > maxRowWidth {
maxRowWidth = rowWidth
}
}
columns := []table.Column{
{
Title: "Repository URL",
Width: getMaxTableRowWidth(rows),
},
{
Title: ri.RepoURL,
Width: len(ri.RepoURL),
},
}
styles := table.DefaultStyles()
styles.Header.MarginTop(1)
styles.Selected = lipgloss.NewStyle()
repoTable := table.New(
table.WithColumns(columns),
table.WithRows(rows),
table.WithHeight(len(rows)),
table.WithStyles(styles),
)
fmt.Println(repoTable.View())
}

func getPullRequestInsights(ctx context.Context, apiClient *client.APIClient, repoID, period int32) (*client.DbPRInsight, error) {
data, _, err := apiClient.PullRequestsServiceAPI.
GetPullRequestInsights(ctx).
RepoIds(fmt.Sprintf("%d", repoID)).
Execute()
if err != nil {
return nil, fmt.Errorf("error while calling 'PullRequestsServiceAPI.GetPullRequestInsights' with repository %d': %w", repoID, err)
}
index := slices.IndexFunc(data, func(insight client.DbPRInsight) bool {
return insight.Interval == period
})
if index == -1 {
return nil, fmt.Errorf("could not find pull request insights for repository %d with interval %d", repoID, period)
}
return &data[index], nil
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/open-sauced/pizza-cli

go 1.20
go 1.21

require (
github.com/charmbracelet/bubbles v0.16.1
Expand Down Expand Up @@ -33,3 +33,5 @@ require (
golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.3.8 // indirect
)

replace github.com/open-sauced/go-api/client v0.0.0-20230825180028-30fe67759eff => github.com/cecobask/go-api/client v0.0.0-20230915140043-f201131f42d3
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/cecobask/go-api/client v0.0.0-20230915140043-f201131f42d3 h1:4iysAcbyHA8SrOcVLjO0E769e//mDkKEnM2MMq0D26Y=
github.com/cecobask/go-api/client v0.0.0-20230915140043-f201131f42d3/go.mod h1:W/TRuLUqYpMvkmElDUQvQ07xlxhK8TOfpwRh8SCAuNA=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v0.24.1 h1:LpdYfnu+Qc6XtvMz6d/6rRY71yttHTP5HtrjMgWvixc=
Expand Down Expand Up @@ -34,8 +36,6 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
github.com/open-sauced/go-api/client v0.0.0-20230825180028-30fe67759eff h1:sHfgCRtAg3xzNXoqqJ2MhDLwPj4GhC99ksDYC+K1BtM=
github.com/open-sauced/go-api/client v0.0.0-20230825180028-30fe67759eff/go.mod h1:W/TRuLUqYpMvkmElDUQvQ07xlxhK8TOfpwRh8SCAuNA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posthog/posthog-go v0.0.0-20230801140217-d607812dee69 h1:01dHVodha5BzrMtVmcpPeA4VYbZEsTXQ6m4123zQXJk=
github.com/posthog/posthog-go v0.0.0-20230801140217-d607812dee69/go.mod h1:migYMxlAqcnQy+3eN8mcL0b2tpKy6R+8Zc0lxwk4dKM=
Expand Down

0 comments on commit 3aae4ff

Please sign in to comment.