Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: provide repository insights #38

Merged
merged 1 commit into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
cecobask marked this conversation as resolved.
Show resolved Hide resolved
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 {
nickytonline marked this conversation as resolved.
Show resolved Hide resolved
// 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]",
cecobask marked this conversation as resolved.
Show resolved Hide resolved
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
Loading