-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(apply): Apply command, and --apply flag for save
- Loading branch information
Showing
20 changed files
with
627 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package api | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/qri-io/qri/api/util" | ||
"github.com/qri-io/qri/lib" | ||
) | ||
|
||
// TransformHandlers connects HTTP requests to the TransformMethods subsystem | ||
type TransformHandlers struct { | ||
*lib.TransformMethods | ||
} | ||
|
||
// NewTransformHandlers constructs a TrasnformHandlers struct | ||
func NewTransformHandlers(inst *lib.Instance) TransformHandlers { | ||
return TransformHandlers{TransformMethods: lib.NewTransformMethods(inst)} | ||
} | ||
|
||
// ApplyHandler is an HTTP handler function for executing a transform script | ||
func (h TransformHandlers) ApplyHandler(prefix string) http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
p := lib.ApplyParams{} | ||
if err := json.NewDecoder(r.Body).Decode(&p); err != nil { | ||
util.WriteErrResponse(w, http.StatusBadRequest, err) | ||
return | ||
} | ||
|
||
res := lib.ApplyResult{} | ||
if err := h.TransformMethods.Apply(&p, &res); err != nil { | ||
util.WriteErrResponse(w, http.StatusBadRequest, err) | ||
return | ||
} | ||
|
||
util.WriteResponse(w, res) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package cmd | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/qri-io/dataset" | ||
"github.com/qri-io/ioes" | ||
"github.com/qri-io/qri/lib" | ||
"github.com/qri-io/qri/repo" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
// NewApplyCommand creates a new `qri apply` cobra command for applying transformations | ||
func NewApplyCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command { | ||
o := &ApplyOptions{IOStreams: ioStreams} | ||
cmd := &cobra.Command{ | ||
Use: "apply", | ||
Short: "apply a transform to a dataset", | ||
Long: `Apply runs a transform script. The result of the transform is displayed after | ||
the command completes. | ||
Nothing is saved in the user's repository.`, | ||
Example: ` # Apply a transform and display the output: | ||
$ qri apply --script transform.star`, | ||
Annotations: map[string]string{ | ||
"group": "dataset", | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
if err := o.Complete(f, args); err != nil { | ||
return err | ||
} | ||
return o.Run() | ||
}, | ||
} | ||
|
||
cmd.Flags().StringVar(&o.FilePath, "file", "", "path of transform script file") | ||
cmd.MarkFlagRequired("file") | ||
cmd.Flags().StringSliceVar(&o.Secrets, "secrets", nil, "transform secrets as comma separated key,value,key,value,... sequence") | ||
|
||
return cmd | ||
} | ||
|
||
// ApplyOptions encapsulates state for the apply command | ||
type ApplyOptions struct { | ||
ioes.IOStreams | ||
|
||
Refs *RefSelect | ||
FilePath string | ||
Secrets []string | ||
|
||
TransformMethods *lib.TransformMethods | ||
} | ||
|
||
// Complete adds any missing configuration that can only be added just before calling Run | ||
func (o *ApplyOptions) Complete(f Factory, args []string) (err error) { | ||
if o.TransformMethods, err = f.TransformMethods(); err != nil { | ||
return err | ||
} | ||
if o.Refs, err = GetCurrentRefSelect(f, args, -1, nil); err != nil { | ||
// This error will be handled during validation | ||
if err != repo.ErrEmptyRef { | ||
return err | ||
} | ||
err = nil | ||
} | ||
o.FilePath, err = filepath.Abs(o.FilePath) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// Run executes the apply command | ||
func (o *ApplyOptions) Run() error { | ||
printRefSelect(o.ErrOut, o.Refs) | ||
|
||
var err error | ||
|
||
if !strings.HasSuffix(o.FilePath, ".star") { | ||
return errors.New("only transform scripts are supported by --file") | ||
} | ||
|
||
tf := dataset.Transform{ | ||
ScriptPath: o.FilePath, | ||
} | ||
|
||
if len(o.Secrets) > 0 { | ||
tf.Secrets, err = parseSecrets(o.Secrets...) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
params := lib.ApplyParams{ | ||
Refstr: o.Refs.Ref(), | ||
Transform: &tf, | ||
ScriptOutput: o.Out, | ||
} | ||
res := lib.ApplyResult{} | ||
if err = o.TransformMethods.Apply(¶ms, &res); err != nil { | ||
return err | ||
} | ||
|
||
data, err := json.MarshalIndent(res.Data, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
printSuccess(o.Out, string(data)) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package cmd | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
) | ||
|
||
func TestTransformApply(t *testing.T) { | ||
run := NewTestRunner(t, "test_peer_transform_apply", "qri_test_transform_apply") | ||
defer run.Delete() | ||
|
||
// Apply a transform which makes a body | ||
output := run.MustExec(t, "qri apply --file testdata/movies/tf_one_movie.star") | ||
expectContains := ` "body": [ | ||
[ | ||
"Spectre", | ||
148 | ||
] | ||
],` | ||
if !strings.Contains(output, expectContains) { | ||
t.Errorf("contents mismatch, want: %s, got: %s", expectContains, output) | ||
} | ||
|
||
// Save a first version with a normal body | ||
run.MustExec(t, "qri save --body testdata/movies/body_ten.csv me/movies") | ||
|
||
// Apply a transform which sets a meta on the existing dataset | ||
output = run.MustExec(t, "qri apply --file testdata/movies/tf_set_meta.star me/movies") | ||
expectContains = `"title": "Did Set Title"` | ||
if !strings.Contains(output, expectContains) { | ||
t.Errorf("contents mismatch, want: %s, got: %s", expectContains, output) | ||
} | ||
} | ||
|
||
func TestApplyRefNotFound(t *testing.T) { | ||
run := NewTestRunner(t, "test_peer_transform_apply", "qri_test_transform_apply") | ||
defer run.Delete() | ||
|
||
// Error to apply a transform using a dataset ref that doesn't exist. | ||
err := run.ExecCommand("qri apply --file testdata/movies/tf_one_movie.star me/not_found") | ||
if err == nil { | ||
t.Errorf("error expected, did not get one") | ||
} | ||
expectErr := `reference not found` | ||
if diff := cmp.Diff(expectErr, err.Error()); diff != "" { | ||
t.Errorf("result mismatch (-want +got):%s\n", diff) | ||
} | ||
} | ||
|
||
func TestApplyMetaOnly(t *testing.T) { | ||
run := NewTestRunner(t, "test_peer_apply_meta_only", "qri_test_apply_meta_only") | ||
defer run.Delete() | ||
|
||
// Apply a transform which sets a meta to the existing dataset | ||
output := run.MustExec(t, "qri apply --file testdata/movies/tf_set_meta.star") | ||
expectContains := `"title": "Did Set Title"` | ||
if !strings.Contains(output, expectContains) { | ||
t.Errorf("contents mismatch, want: %s, got: %s", expectContains, output) | ||
} | ||
} | ||
|
||
func TestApplyModifyBody(t *testing.T) { | ||
run := NewTestRunner(t, "test_peer_apply_mod_body", "qri_test_apply_mod_body") | ||
defer run.Delete() | ||
|
||
// Save two versions, the second of which uses get_body in a transformation | ||
run.MustExec(t, "qri save --body=testdata/movies/body_two.json me/test_ds") | ||
output := run.MustExec(t, "qri apply --file=testdata/movies/tf_add_one.star me/test_ds") | ||
expectContains := `"body": [ | ||
[ | ||
"Avatar", | ||
179 | ||
], | ||
[ | ||
"Pirates of the Caribbean: At World's End", | ||
170 | ||
] | ||
],` | ||
if !strings.Contains(output, expectContains) { | ||
t.Errorf("contents mismatch, want: %s, got: %s", expectContains, output) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.