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 28, 2023
1 parent b5d6ab3 commit e6f3738
Show file tree
Hide file tree
Showing 13 changed files with 474 additions and 88 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ Available Commands:
insights Gather insights about git contributors, repositories, users and pull requests
login Log into the CLI application via GitHub
repo-query Ask questions about a GitHub repository
show Get visual metrics of a repository
version Displays the build version of the CLI
Flags:
--beta Shorthand for using the beta OpenSauced API endpoint ("https://beta.api.opensauced.pizza"). Supersedes the '--endpoint' flag
--disable-telemetry Disable sending telemetry data to OpenSauced
-e, --endpoint string The API endpoint to send requests to (default "https://api.opensauced.pizza")
-h, --help help for pizza
-o, --output string The formatting style for command output (default "table")
Use "pizza [command] --help" for more information about a command.
```
Expand Down
177 changes: 112 additions & 65 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,128 @@ 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.Println(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:"-" yaml:"-"`
New []string `json:"new" yaml:"new"`
Recent []string `json:"recent" yaml:"recent"`
Alumni []string `json:"alumni" yaml:"alumni"`
Repeat []string `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{
{
"New contributors",
strconv.Itoa(len(cis[i].New)),
},
{
"Recent contributors",
strconv.Itoa(len(cis[i].Recent)),
},
{
"Alumni contributors",
strconv.Itoa(len(cis[i].Alumni)),
},
{
"Repeat contributors",
strconv.Itoa(len(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 +193,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 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 e6f3738

Please sign in to comment.