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 27, 2023
1 parent b5d6ab3 commit 0f0f1dc
Show file tree
Hide file tree
Showing 12 changed files with 441 additions and 100 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
with:
go-version: 1.21
go-version: 1.21.x

- name: Check out code
uses: actions/checkout@v3
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ jobs:
- name: Set up Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
with:
go-version: 1.20.x
go-version: 1.21.x
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.53
version: v1.54.2

test:
runs-on: ubuntu-latest
Expand All @@ -36,7 +36,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
with:
go-version: 1.20.x
go-version: 1.21.x
- name: Test
run: make test

Expand All @@ -47,6 +47,6 @@ jobs:
- name: Set up Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
with:
go-version: 1.20.x
go-version: 1.21.x
- name: Build go binary
run: make build
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ lint:
--rm \
-v ./:/app \
-w /app \
golangci/golangci-lint:v1.53.3 \
golangci/golangci-lint:v1.54.2 \
golangci-lint run -v

test:
Expand Down
176 changes: 99 additions & 77 deletions cmd/insights/contributors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import (
"fmt"
"net/http"
"strconv"
"strings"
"sync"

"github.com/charmbracelet/bubbles/table"
"github.com/charmbracelet/lipgloss"
bubblesTable "github.com/charmbracelet/bubbles/table"
"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/open-sauced/pizza-cli/pkg/utils"
"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 @@ -29,13 +29,16 @@ type Options struct {

// Period is the number of days, used for query filtering
Period int32

// Output is the formatting style for command output
Output string
}

// 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,84 +52,111 @@ 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)
output, _ := cmd.Flags().GetString(constants.FlagNameOutput)
opts.Output = output
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
}
var (
waitGroup = new(sync.WaitGroup)
errorChan = make(chan error, len(repositories))
waitGroup = new(sync.WaitGroup)
errorChan = make(chan error, len(repositories))
insightsChan = make(chan contributorsInsights, len(repositories))
doneChan = make(chan struct{})
insights = make(contributorsInsightsSlice, 0, len(repositories))
allErrors error
)
for url := range repositories {
repoURL := url
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
repoContributorsInsights, err := findAllRepositoryContributorsInsights(context.TODO(), opts, repoURL)
go func() {
for url := range repositories {
repoURL := url
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
allData, err := findAllContributorsInsights(ctx, opts, repoURL)
if err != nil {
errorChan <- err
return
}
if allData == nil {
return
}
insightsChan <- *allData
}()
}
waitGroup.Wait()
close(doneChan)
}()
for {
select {
case err = <-errorChan:
allErrors = errors.Join(allErrors, err)
case data := <-insightsChan:
insights = append(insights, data)
case <-doneChan:
if allErrors != nil {
return allErrors
}
output, err := insights.BuildOutput(opts.Output)
if err != nil {
errorChan <- err
return
return err
}
repoContributorsInsights.RenderTable()
}()
}
waitGroup.Wait()
close(errorChan)
var allErrors error
for err = range errorChan {
allErrors = errors.Join(allErrors, err)
fmt.Print(output)
return nil
}
}
return allErrors
}

type repositoryContributorsInsights struct {
RepoID int32
RepoURL string
New []string
Recent []string
Alumni []string
Repeat []string
type contributorsInsights struct {
RepoURL string `json:"repo_url" yaml:"repo_url"`
RepoID int `json:"repo_id" yaml:"repo_id"`
New int `json:"new" yaml:"new"`
Recent int `json:"recent" yaml:"recent"`
Alumni int `json:"alumni" yaml:"alumni"`
Repeat int `json:"repeat" yaml:"repeat"`
}

func (rci *repositoryContributorsInsights) RenderTable() {
if rci == nil {
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))},
type contributorsInsightsSlice []contributorsInsights

func (cis contributorsInsightsSlice) BuildOutput(format string) (string, error) {
switch format {
case constants.OutputTable:
return cis.OutputTable()
case constants.OutputJSON:
return utils.OutputJSON(cis)
case constants.OutputYAML:
return utils.OutputYAML(cis)
default:
return "", fmt.Errorf("unknown output format %s", format)
}
columns := []table.Column{
{
Title: "Repository URL",
Width: 20,
},
{
Title: rci.RepoURL,
Width: len(rci.RepoURL),
},
}

func (cis contributorsInsightsSlice) OutputTable() (string, error) {
tables := make([]string, 0, len(cis))
for i := range cis {
rows := []bubblesTable.Row{
{"Repository ID", strconv.Itoa(cis[i].RepoID)},
{"New contributors", strconv.Itoa(cis[i].New)},
{"Recent contributors", strconv.Itoa(cis[i].Recent)},
{"Alumni contributors", strconv.Itoa(cis[i].Alumni)},
{"Repeat contributors", strconv.Itoa(cis[i].Repeat)},
}
columns := []bubblesTable.Column{
{Title: "Repository URL", Width: utils.GetMaxTableRowWidth(rows)},
{Title: cis[i].RepoURL, Width: len(cis[i].RepoURL)},
}
tables = append(tables, utils.OutputTable(rows, columns))
}
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())
separator := fmt.Sprintf("\n%s\n", strings.Repeat("―", 3))
return strings.Join(tables, separator), nil
}

func findRepositoryByOwnerAndRepoName(ctx context.Context, apiClient *client.APIClient, repoURL string) (*client.DbRepo, error) {
Expand All @@ -146,17 +176,17 @@ func findRepositoryByOwnerAndRepoName(ctx context.Context, apiClient *client.API
return repo, nil
}

func findAllRepositoryContributorsInsights(ctx context.Context, opts *Options, repoURL string) (*repositoryContributorsInsights, error) {
func findAllContributorsInsights(ctx context.Context, opts *contributorsOptions, repoURL string) (*contributorsInsights, 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)
}
if repo == nil {
return nil, nil
}
repoContributorsInsights := &repositoryContributorsInsights{
RepoID: repo.Id,
RepoURL: repoURL,
repoContributorsInsights := &contributorsInsights{
RepoID: int(repo.Id),
RepoURL: repo.SvnUrl,
}
var (
waitGroup = new(sync.WaitGroup)
Expand All @@ -170,9 +200,7 @@ func findAllRepositoryContributorsInsights(ctx context.Context, opts *Options, r
errorChan <- err
return
}
for _, data := range response.Data {
repoContributorsInsights.New = append(repoContributorsInsights.New, data.AuthorLogin)
}
repoContributorsInsights.New = len(response.Data)
}()
waitGroup.Add(1)
go func() {
Expand All @@ -182,9 +210,7 @@ func findAllRepositoryContributorsInsights(ctx context.Context, opts *Options, r
errorChan <- err
return
}
for _, data := range response.Data {
repoContributorsInsights.Recent = append(repoContributorsInsights.Recent, data.AuthorLogin)
}
repoContributorsInsights.Recent = len(response.Data)
}()
waitGroup.Add(1)
go func() {
Expand All @@ -194,9 +220,7 @@ func findAllRepositoryContributorsInsights(ctx context.Context, opts *Options, r
errorChan <- err
return
}
for _, data := range response.Data {
repoContributorsInsights.Alumni = append(repoContributorsInsights.Alumni, data.AuthorLogin)
}
repoContributorsInsights.Alumni = len(response.Data)
}()
waitGroup.Add(1)
go func() {
Expand All @@ -206,9 +230,7 @@ func findAllRepositoryContributorsInsights(ctx context.Context, opts *Options, r
errorChan <- err
return
}
for _, data := range response.Data {
repoContributorsInsights.Repeat = append(repoContributorsInsights.Repeat, data.AuthorLogin)
}
repoContributorsInsights.Repeat = len(response.Data)
}()
waitGroup.Wait()
close(errorChan)
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(NewRepositoriesCommand())
return cmd
}
Loading

0 comments on commit 0f0f1dc

Please sign in to comment.