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: pizza generate insight command #179

Merged
merged 1 commit into from
Sep 12, 2024
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
190 changes: 2 additions & 188 deletions cmd/generate/codeowners/codeowners.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ import (
"github.com/jpmcb/gopherlogs/pkg/colors"
"github.com/spf13/cobra"

"github.com/open-sauced/pizza-cli/api"
"github.com/open-sauced/pizza-cli/api/auth"
"github.com/open-sauced/pizza-cli/api/services/workspaces"
"github.com/open-sauced/pizza-cli/api/services/workspaces/userlists"
"github.com/open-sauced/pizza-cli/pkg/config"
"github.com/open-sauced/pizza-cli/pkg/constants"
"github.com/open-sauced/pizza-cli/pkg/logging"
Expand All @@ -33,9 +29,6 @@ type Options struct {
// the number of days to look back
previousDays int

// the session token adding codeowners to a workspace contributor list
token string

logger gopherlogs.Logger
tty bool
loglevel int
Expand Down Expand Up @@ -194,188 +187,9 @@ func run(opts *Options, cmd *cobra.Command) error {
opts.logger.V(logging.LogInfo).Style(0, colors.FgGreen).Infof("Finished generating file: %s\n", outputPath)
_ = opts.telemetry.CaptureCodeownersGenerate()

// ignore the interactive prompts for CI/CD environments
if opts.tty {
return nil
}

// 1. Ask if they want to add users to a list
var input string
fmt.Print("Do you want to add these codeowners to an OpenSauced Contributor Insight? (y/n): ")
_, err = fmt.Scanln(&input)
if err != nil {
return fmt.Errorf("could not scan input from terminal: %w", err)
}

switch input {
case "y", "Y", "yes":
opts.logger.V(logging.LogInfo).Style(0, colors.FgGreen).Infof("Adding codeowners to contributor insight\n")
case "n", "N", "no":
return nil
default:
return errors.New("invalid answer. Please enter y or n")
}

// 2. Check if user is logged in. Log them in if not.
opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Initiating log in flow\n")
authenticator := auth.NewAuthenticator()
err = authenticator.CheckSession()
if err != nil {
opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Log in session invalid: %s\n", err)
fmt.Print("Do you want to log into OpenSauced? (y/n): ")
_, err := fmt.Scanln(&input)
if err != nil {
return fmt.Errorf("could not scan input from terminal: %w", err)
}

switch input {
case "y", "Y", "yes":
user, err := authenticator.Login()
if err != nil {
_ = opts.telemetry.CaptureFailedCodeownersGenerateAuth()
opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error logging in\n")
return fmt.Errorf("could not log in: %w", err)
}
_ = opts.telemetry.CaptureCodeownersGenerateAuth(user)
opts.logger.V(logging.LogInfo).Style(0, colors.FgGreen).Infof("Logged in as: %s\n", user)

case "n", "N", "no":
return nil

default:
return errors.New("invalid answer. Please enter y or n")
}
}

opts.token, err = authenticator.GetSessionToken()
if err != nil {
_ = opts.telemetry.CaptureFailedCodeownersGenerateContributorInsight()
opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error getting session token\n")
return fmt.Errorf("could not get session token: %w", err)
}

listName := filepath.Base(opts.path)

opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Looking up OpenSauced workspace: Pizza CLI\n")
workspace, err := findCreatePizzaCliWorkspace(opts)
if err != nil {
_ = opts.telemetry.CaptureFailedCodeownersGenerateContributorInsight()
opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error finding Workspace: Pizza CLI\n")
return fmt.Errorf("could not find Pizza CLI workspace: %w", err)
}
opts.logger.V(logging.LogDebug).Style(0, colors.FgGreen).Infof("Found workspace: Pizza CLI\n")

opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Looking up Contributor Insight for local repository: %s\n", listName)
userList, err := updateCreateLocalWorkspaceUserList(opts, listName, workspace, codeowners)
if err != nil {
_ = opts.telemetry.CaptureFailedCodeownersGenerateContributorInsight()
opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error finding Workspace Contributor Insight: %s\n", listName)
return fmt.Errorf("could not find Workspace Contributor Insight: %s - %w", listName, err)
}
opts.logger.V(logging.LogDebug).Style(0, colors.FgGreen).Infof("Updated Contributor Insight for local repository: %s\n", listName)
opts.logger.V(logging.LogInfo).Style(0, colors.FgCyan).Infof("Access list on OpenSauced:\n%s\n", fmt.Sprintf("https://app.opensauced.pizza/workspaces/%s/contributor-insights/%s", workspace.ID, userList.ID))
opts.logger.V(logging.LogInfo).Style(0, colors.FgCyan).Infof("\nCreate an OpenSauced Contributor Insight to get metrics and insights on these codeowners:\n")
opts.logger.V(logging.LogInfo).Style(0, colors.FgCyan).Infof("$ pizza generate insight " + opts.path + "\n")
_ = opts.telemetry.CaptureCodeownersGenerateContributorInsight()

return nil
}

// findCreatePizzaCliWorkspace finds or creates a "Pizza CLI" workspace
// for the authenticated user
func findCreatePizzaCliWorkspace(opts *Options) (*workspaces.DbWorkspace, error) {
nextPage := true
page := 1
apiClient := api.NewClient("https://api.opensauced.pizza")

for nextPage {
opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Query user workspaces page: %d\n", page)
workspaceResp, _, err := apiClient.WorkspacesService.GetWorkspaces(opts.token, page, 100)
if err != nil {
return nil, err
}

for _, workspace := range workspaceResp.Data {
if workspace.Name == "Pizza CLI" {
opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Found existing workspace named: Pizza CLI\n")
return &workspace, nil
}
}

nextPage = workspaceResp.Meta.HasNextPage
page++
}

opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Creating new user workspace: Pizza CLI\n")
newWorkspace, _, err := apiClient.WorkspacesService.CreateWorkspaceForUser(opts.token, "Pizza CLI", "A workspace for the Pizza CLI", []string{})
if err != nil {
return nil, err
}

return newWorkspace, nil
}

// updateCreateLocalWorkspaceUserList updates or creates a workspace contributor list
// for the authenticated user with the given codeowners
func updateCreateLocalWorkspaceUserList(opts *Options, listName string, workspace *workspaces.DbWorkspace, codeowners FileStats) (*userlists.DbUserList, error) {
nextPage := true
page := 1
apiClient := api.NewClient("https://api.opensauced.pizza")

var targetUserListID string

for nextPage {
opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Query user Workspace Contributor Insight page: %d\n", page)
userListsResp, _, err := apiClient.WorkspacesService.UserListService.GetUserLists(opts.token, workspace.ID, page, 100)
if err != nil {
return nil, err
}

nextPage = userListsResp.Meta.HasNextPage
page++

for _, userList := range userListsResp.Data {
if userList.Name == listName {
opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Found existing Workspace Contributor Insight named: %s\n", listName)
targetUserListID = userList.ID
nextPage = false
}
}
}

if targetUserListID == "" {
var err error

opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Creating new user Workspace Contributor List: %s\n", listName)
createdUserList, _, err := apiClient.WorkspacesService.UserListService.CreateUserListForUser(opts.token, workspace.ID, listName, []string{})
if err != nil {
return nil, err
}

targetUserListID = createdUserList.UserListID
}

targetUserList, _, err := apiClient.WorkspacesService.UserListService.GetUserList(opts.token, workspace.ID, targetUserListID)
if err != nil {
return nil, err
}

// create a mapping of author logins to empty structs (i.e., a unique set).
// this de-structures the { filename: author-stats } mapping that originally
// built the codeowners
uniqueLogins := make(map[string]struct{})
for _, codeowner := range codeowners {
for _, k := range codeowner {
if k.GitHubAlias != "" {
uniqueLogins[k.GitHubAlias] = struct{}{}
}
}
}

logins := []string{}
for login := range uniqueLogins {
logins = append(logins, login)
}

opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Updating Contributor Insight with codeowners with GitHub aliases: %v\n", logins)
userlist, _, err := apiClient.WorkspacesService.UserListService.PatchUserListForUser(opts.token, workspace.ID, targetUserList.ID, targetUserList.Name, logins)
return userlist, err
}
2 changes: 2 additions & 0 deletions cmd/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/open-sauced/pizza-cli/cmd/generate/codeowners"
"github.com/open-sauced/pizza-cli/cmd/generate/config"
"github.com/open-sauced/pizza-cli/cmd/generate/insight"
)

const generateLongDesc string = `The 'generate' command provides tools to automate the creation of important project documentation and derive insights from your codebase.`
Expand All @@ -28,6 +29,7 @@ func NewGenerateCommand() *cobra.Command {

cmd.AddCommand(codeowners.NewCodeownersCommand())
cmd.AddCommand(config.NewConfigCommand())
cmd.AddCommand(insight.NewGenerateInsightCommand())

return cmd
}
Expand Down
Loading
Loading