interactive ruleset selection, move shared logic
This commit is contained in:
parent
ef30d875b5
commit
f25b2af053
4 changed files with 122 additions and 30 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue