interactive ruleset selection, move shared logic

This commit is contained in:
vaindil 2023-05-04 14:16:34 -04:00
parent ef30d875b5
commit f25b2af053
4 changed files with 122 additions and 30 deletions

View file

@ -137,20 +137,8 @@ func listRun(opts *ListOptions) error {
return err
}
var entityName string
if opts.Organization != "" {
entityName = opts.Organization
} else {
entityName = ghrepo.FullName(repoI)
}
if result.TotalCount == 0 {
parentsMsg := ""
if opts.IncludeParents {
parentsMsg = " or its parents"
}
msg := fmt.Sprintf("no rulesets found in %s%s", entityName, parentsMsg)
return cmdutil.NewNoResultsError(msg)
return shared.NoRulesetsFoundError(opts.Organization, repoI, opts.IncludeParents)
}
opts.IO.DetectTerminalTheme()
@ -163,7 +151,13 @@ func listRun(opts *ListOptions) error {
cs := opts.IO.ColorScheme()
if opts.IO.IsStdoutTTY() {
fmt.Fprintf(opts.IO.Out, "\nShowing %d of %d rulesets in %s\n\n", len(result.Rulesets), result.TotalCount, entityName)
parentsMsg := ""
if opts.IncludeParents {
parentsMsg = " and its parents"
}
inMsg := fmt.Sprintf("%s%s", shared.EntityName(opts.Organization, repoI), parentsMsg)
fmt.Fprintf(opts.IO.Out, "\nShowing %d of %d rulesets in %s\n\n", len(result.Rulesets), result.TotalCount, inMsg)
}
tp := tableprinter.New(opts.IO)
@ -172,15 +166,7 @@ func listRun(opts *ListOptions) error {
for _, rs := range result.Rulesets {
tp.AddField(strconv.Itoa(rs.DatabaseId))
tp.AddField(rs.Name, tableprinter.WithColor(cs.Bold))
var ownerString string
if rs.Source.RepoOwner != "" {
ownerString = fmt.Sprintf("%s (repo)", rs.Source.RepoOwner)
} else if rs.Source.OrgOwner != "" {
ownerString = fmt.Sprintf("%s (org)", rs.Source.OrgOwner)
} else {
ownerString = "(unknown)"
}
tp.AddField(ownerString)
tp.AddField(shared.RulesetSource(rs))
tp.AddField(strings.ToLower(rs.Enforcement))
tp.AddField(strings.ToLower(rs.Target))
tp.AddField(strconv.Itoa(rs.Rules.TotalCount))

View file

@ -108,8 +108,9 @@ func rulesetsQuery(org bool) string {
target
enforcement
source {
... on Repository { repoOwner: nameWithOwner }
... on Organization { orgOwner: login }
__typename
... on Repository { owner: nameWithOwner }
... on Organization { owner: login }
}
rules {
totalCount

View file

@ -1,13 +1,20 @@
package shared
import (
"fmt"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmdutil"
)
type RulesetGraphQL struct {
DatabaseId int
Name string
Target string
Enforcement string
Source struct {
RepoOwner string
OrgOwner string
TypeName string `json:"__typename"`
Owner string
}
Rules struct {
TotalCount int
@ -30,3 +37,33 @@ type RulesetREST struct {
Source string
Rules []struct{}
}
// Returns the source of the ruleset in the format "owner/name (repo)" or "owner (org)"
func RulesetSource(rs RulesetGraphQL) string {
var level string
if rs.Source.TypeName == "Repository" {
level = "repo"
} else if rs.Source.TypeName == "Organization" {
level = "org"
} else {
level = "unknown"
}
return fmt.Sprintf("%s (%s)", rs.Source.Owner, level)
}
func NoRulesetsFoundError(orgOption string, repoI ghrepo.Interface, includeParents bool) error {
entityName := EntityName(orgOption, repoI)
parentsMsg := ""
if includeParents {
parentsMsg = " or its parents"
}
return cmdutil.NewNoResultsError(fmt.Sprintf("no rulesets found in %s%s", entityName, parentsMsg))
}
func EntityName(orgOption string, repoI ghrepo.Interface) string {
if orgOption != "" {
return orgOption
}
return ghrepo.FullName(repoI)
}

View file

@ -6,12 +6,14 @@ import (
"reflect"
"sort"
"strconv"
"strings"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/internal/browser"
"github.com/cli/cli/v2/internal/config"
"github.com/cli/cli/v2/internal/ghinstance"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/internal/text"
"github.com/cli/cli/v2/pkg/cmd/ruleset/shared"
"github.com/cli/cli/v2/pkg/cmdutil"
@ -25,10 +27,13 @@ type ViewOptions struct {
Config func() (config.Config, error)
BaseRepo func() (ghrepo.Interface, error)
Browser browser.Browser
Prompter prompter.Prompter
ID string
WebMode bool
Organization string
ID string
WebMode bool
IncludeParents bool
InteractiveMode bool
Organization string
}
func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Command {
@ -37,6 +42,7 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman
HttpClient: f.HttpClient,
Browser: f.Browser,
Config: f.Config,
Prompter: f.Prompter,
}
cmd := &cobra.Command{
@ -49,6 +55,9 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman
the ruleset to view.
`),
Example: heredoc.Doc(`
# Interactively choose a ruleset to view
$ gh ruleset view
# View a ruleset in the current repository
$ gh ruleset view 43
@ -75,6 +84,10 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman
return cmdutil.FlagErrorf("invalid value for ruleset ID: %v is not an integer", args[0])
}
opts.ID = args[0]
} else if !opts.IO.CanPrompt() {
return cmdutil.FlagErrorf("a ruleset ID must be provided when not running interactively")
} else {
opts.InteractiveMode = true
}
if runF != nil {
@ -86,6 +99,7 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman
cmd.Flags().BoolVarP(&opts.WebMode, "web", "w", false, "Open the ruleset in the browser")
cmd.Flags().StringVarP(&opts.Organization, "org", "o", "", "Organization name if the provided ID is an organization-level ruleset")
cmd.Flags().BoolVarP(&opts.IncludeParents, "parents", "p", false, "When choosing interactively, include rulesets configured at higher levels that also apply")
return cmd
}
@ -108,6 +122,38 @@ func viewRun(opts *ViewOptions) error {
hostname, _ := cfg.DefaultHost()
if opts.InteractiveMode {
var rsList *shared.RulesetList
limit := 30
if opts.Organization != "" {
rsList, err = shared.ListOrgRulesets(httpClient, opts.Organization, limit, hostname, opts.IncludeParents)
} else {
rsList, err = shared.ListRepoRulesets(httpClient, repoI, limit, opts.IncludeParents)
}
if err != nil {
return err
}
if rsList.TotalCount == 0 {
return shared.NoRulesetsFoundError(opts.Organization, repoI, opts.IncludeParents)
}
rs, err := selectRulesetID(rsList, opts.Prompter)
if err != nil {
return err
}
if rs != nil {
opts.ID = strconv.Itoa(rs.DatabaseId)
// can't get a ruleset lower in the chain than what was queried, so no need to handle repos here
if rs.Source.TypeName == "Organization" {
opts.Organization = rs.Source.Owner
}
}
}
var rs *shared.RulesetREST
if opts.Organization != "" {
rs, err = viewOrgRuleset(httpClient, opts.Organization, opts.ID, hostname)
@ -232,3 +278,25 @@ func viewRun(opts *ViewOptions) error {
return nil
}
func selectRulesetID(rsList *shared.RulesetList, p prompter.Prompter) (*shared.RulesetGraphQL, error) {
rulesets := make([]string, len(rsList.Rulesets))
for i, rs := range rsList.Rulesets {
s := fmt.Sprintf(
"%d: %s | %s | contains %s | configured in %s",
rs.DatabaseId,
rs.Name,
strings.ToLower(rs.Enforcement),
text.Pluralize(rs.Rules.TotalCount, "rule"),
shared.RulesetSource(rs),
)
rulesets[i] = s
}
r, err := p.Select("Which ruleset would you like to view?", rulesets[0], rulesets)
if err != nil {
return nil, err
}
return &rsList.Rulesets[r], nil
}