Use interactive prompt to select resource to run if not specified (#762)

## Changes

Display an interactive prompt with a list of resources to run if one
isn't specified and the command is run interactively.

## Tests

Manually confirmed:
* The new prompt works
* Shell completion still works
* Specifying a key argument still works
This commit is contained in:
Pieter Noordhuis 2023-09-11 20:03:12 +02:00 committed by GitHub
parent 0cb05d1ded
commit a2775f836f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 57 additions and 12 deletions

View File

@ -95,6 +95,13 @@ type jobRunner struct {
job *resources.Job job *resources.Job
} }
func (r *jobRunner) Name() string {
if r.job == nil || r.job.JobSettings == nil {
return ""
}
return r.job.JobSettings.Name
}
func isFailed(task jobs.RunTask) bool { func isFailed(task jobs.RunTask) bool {
return task.State.LifeCycleState == jobs.RunLifeCycleStateInternalError || return task.State.LifeCycleState == jobs.RunLifeCycleStateInternalError ||
(task.State.LifeCycleState == jobs.RunLifeCycleStateTerminated && (task.State.LifeCycleState == jobs.RunLifeCycleStateTerminated &&

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle"
"golang.org/x/exp/maps"
) )
// RunnerLookup maps identifiers to a list of workloads that match that identifier. // RunnerLookup maps identifiers to a list of workloads that match that identifier.
@ -32,18 +33,20 @@ func ResourceKeys(b *bundle.Bundle) (keyOnly RunnerLookup, keyWithType RunnerLoo
return return
} }
// ResourceCompletions returns a list of keys that unambiguously reference resources in the bundle. // ResourceCompletionMap returns a map of resource keys to their respective names.
func ResourceCompletions(b *bundle.Bundle) []string { func ResourceCompletionMap(b *bundle.Bundle) map[string]string {
seen := make(map[string]bool) out := make(map[string]string)
comps := []string{}
keyOnly, keyWithType := ResourceKeys(b) keyOnly, keyWithType := ResourceKeys(b)
// Keep track of resources we have seen by their fully qualified key.
seen := make(map[string]bool)
// First add resources that can be identified by key alone. // First add resources that can be identified by key alone.
for k, v := range keyOnly { for k, v := range keyOnly {
// Invariant: len(v) >= 1. See [ResourceKeys]. // Invariant: len(v) >= 1. See [ResourceKeys].
if len(v) == 1 { if len(v) == 1 {
seen[v[0].Key()] = true seen[v[0].Key()] = true
comps = append(comps, k) out[k] = v[0].Name()
} }
} }
@ -54,8 +57,13 @@ func ResourceCompletions(b *bundle.Bundle) []string {
if ok { if ok {
continue continue
} }
comps = append(comps, k) out[k] = v[0].Name()
} }
return comps return out
}
// ResourceCompletions returns a list of keys that unambiguously reference resources in the bundle.
func ResourceCompletions(b *bundle.Bundle) []string {
return maps.Keys(ResourceCompletionMap(b))
} }

View File

@ -136,6 +136,13 @@ type pipelineRunner struct {
pipeline *resources.Pipeline pipeline *resources.Pipeline
} }
func (r *pipelineRunner) Name() string {
if r.pipeline == nil || r.pipeline.PipelineSpec == nil {
return ""
}
return r.pipeline.PipelineSpec.Name
}
func (r *pipelineRunner) Run(ctx context.Context, opts *Options) (output.RunOutput, error) { func (r *pipelineRunner) Run(ctx context.Context, opts *Options) (output.RunOutput, error) {
var pipelineID = r.pipeline.ID var pipelineID = r.pipeline.ID

View File

@ -21,6 +21,9 @@ type Runner interface {
// This is used for showing the user hints w.r.t. disambiguation. // This is used for showing the user hints w.r.t. disambiguation.
Key() string Key() string
// Name returns the resource's name, if defined.
Name() string
// Run the underlying worklow. // Run the underlying worklow.
Run(ctx context.Context, opts *Options) (output.RunOutput, error) Run(ctx context.Context, opts *Options) (output.RunOutput, error)
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/databricks/cli/bundle/phases" "github.com/databricks/cli/bundle/phases"
"github.com/databricks/cli/bundle/run" "github.com/databricks/cli/bundle/run"
"github.com/databricks/cli/cmd/root" "github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/flags" "github.com/databricks/cli/libs/flags"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,9 +17,9 @@ import (
func newRunCommand() *cobra.Command { func newRunCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "run [flags] KEY", Use: "run [flags] KEY",
Short: "Run a workload (e.g. a job or a pipeline)", Short: "Run a resource (e.g. a job or a pipeline)",
Args: cobra.ExactArgs(1), Args: cobra.MaximumNArgs(1),
PreRunE: ConfigureBundleWithVariables, PreRunE: ConfigureBundleWithVariables,
} }
@ -29,9 +30,10 @@ func newRunCommand() *cobra.Command {
cmd.Flags().BoolVar(&noWait, "no-wait", false, "Don't wait for the run to complete.") cmd.Flags().BoolVar(&noWait, "no-wait", false, "Don't wait for the run to complete.")
cmd.RunE = func(cmd *cobra.Command, args []string) error { cmd.RunE = func(cmd *cobra.Command, args []string) error {
b := bundle.Get(cmd.Context()) ctx := cmd.Context()
b := bundle.Get(ctx)
err := bundle.Apply(cmd.Context(), b, bundle.Seq( err := bundle.Apply(ctx, b, bundle.Seq(
phases.Initialize(), phases.Initialize(),
terraform.Interpolate(), terraform.Interpolate(),
terraform.Write(), terraform.Write(),
@ -42,13 +44,31 @@ func newRunCommand() *cobra.Command {
return err return err
} }
// If no arguments are specified, prompt the user to select something to run.
if len(args) == 0 && cmdio.IsInteractive(ctx) {
// Invert completions from KEY -> NAME, to NAME -> KEY.
inv := make(map[string]string)
for k, v := range run.ResourceCompletionMap(b) {
inv[v] = k
}
id, err := cmdio.Select(ctx, inv, "Resource to run")
if err != nil {
return err
}
args = append(args, id)
}
if len(args) != 1 {
return fmt.Errorf("expected a KEY of the resource to run")
}
runner, err := run.Find(b, args[0]) runner, err := run.Find(b, args[0])
if err != nil { if err != nil {
return err return err
} }
runOptions.NoWait = noWait runOptions.NoWait = noWait
output, err := runner.Run(cmd.Context(), &runOptions) output, err := runner.Run(ctx, &runOptions)
if err != nil { if err != nil {
return err return err
} }