Skip to content
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
94 changes: 57 additions & 37 deletions cmd/root_cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,19 @@ Examples:
# Push a new version non-interactively
leap push -n my-model -m ./model.h5

# Overwrite an existing version by id, run eval (auto-detects changes)
# Overwrite an existing version by id, then prompt for what changed
leap push -o 6a16a0cf -e

# Overwrite by name — picker opens if the name is ambiguous
leap push -o my-model -e

# Overwrite + force a specific artifact refresh (implies --eval)
# Overwrite + name what changed (implies --eval; skips the prompt)
leap push -o my-model -u viz
leap push -o my-model -u metric_direction
leap push -o my-model -u metric # triggers a full re-evaluation

# Refresh multiple artifacts in one run
leap push -o my-model -u metadata -u insights
leap push -o my-model -u visualization -u insights
`,
RunE: func(cmd *cobra.Command, args []string) error {
return runPush(cmd.Context(), in)
Expand All @@ -91,7 +93,7 @@ Examples:
cmd.Flags().StringVarP(&in.overwriteVersionRef, "overwrite", "o", "", "Overwrite an existing version (id, or name — picker shown if name is ambiguous)")
cmd.Flags().StringVar(&in.overwriteVersionRef, "overwrite-version", "", "")
_ = cmd.Flags().MarkDeprecated("overwrite-version", "use --overwrite (-o) instead")
cmd.Flags().StringSliceVarP(&in.updateParts, "update", "u", nil, "Artifact(s) to refresh on overwrite (repeatable; implies --eval). Values: metadata, metric, insights, visualization (viz)")
cmd.Flags().StringSliceVarP(&in.updateParts, "update", "u", nil, "What changed in the code on overwrite (repeatable; implies --eval; skips the prompt). Values: metadata, metric, metric_direction, insights, visualization (viz). metadata+metric trigger a full re-evaluation.")
return cmd
}

Expand Down Expand Up @@ -122,7 +124,7 @@ func runPush(cmdCtx context.Context, in *pushInputs) error {
return err
}

evalBatchSize, updateActions, runUpdateEvaluate, err := s.resolveEvalPlan()
dispatch, err := s.resolveEvalPlan()
if err != nil {
return err
}
Expand All @@ -138,7 +140,7 @@ func runPush(cmdCtx context.Context, in *pushInputs) error {

s.sendSuccessEvent(codeResp, codePushed)

return s.triggerEvaluate(codeResp.VersionId, evalBatchSize, updateActions, runUpdateEvaluate)
return s.triggerEvaluate(codeResp.VersionId, dispatch)
}

func validatePushInputs(in *pushInputs) error {
Expand Down Expand Up @@ -273,50 +275,62 @@ func (s *pushState) syncBranchSecretAndPython() error {
return nil
}

func (s *pushState) resolveEvalPlan() (batchSize int, updateActions []tensorleapapi.UpdateAction, runUpdateEvaluate bool, err error) {
type evalDispatch struct {
batchSize int
updateActions []tensorleapapi.UpdateAction
runUpdateEvaluate bool
persistOnly bool
}

func (s *pushState) resolveEvalPlan() (evalDispatch, error) {
in := s.inputs
if len(in.updateParts) > 0 && !s.isOverwrite {
err = fmt.Errorf("--update (-u) only applies when overwriting an existing version (use --overwrite or choose overwrite in the prompt)")
return
}
if !in.runEval {
return
return evalDispatch{}, fmt.Errorf("--update (-u) only applies when overwriting an existing version (use --overwrite or choose overwrite in the prompt)")
}

if !s.isOverwrite {
batchSize, err = s.askOrDefaultBatchSize()
return
if !in.runEval {
return evalDispatch{}, nil
}
batchSize, err := s.askOrDefaultBatchSize()
return evalDispatch{batchSize: batchSize}, err
}

plan, planErr := s.resolveOverwriteEvalPlan()
if planErr != nil {
err = planErr
return
plan, err := s.resolveOverwriteEvalPlan()
if err != nil {
return evalDispatch{}, err
}

if !in.runEval {
return evalDispatch{updateActions: plan.UpdateActions, persistOnly: true}, nil
}

if plan.Kind == model.EvaluatePlanReset {
batchSize, err = s.askOrDefaultBatchSize()
return
batchSize, err := s.askOrDefaultBatchSize()
return evalDispatch{batchSize: batchSize, updateActions: plan.UpdateActions}, err
}
updateActions = plan.UpdateActions
runUpdateEvaluate = true
return
return evalDispatch{updateActions: plan.UpdateActions, runUpdateEvaluate: true}, nil
}

func (s *pushState) resolveOverwriteEvalPlan() (model.EvaluatePlan, error) {
plan, err := s.collectUserUpdatePlan()
if err != nil {
return model.EvaluatePlan{}, err
}
if s.inputs.runEval && plan.Kind == model.EvaluatePlanUpdate && !s.canUpdateEvaluate() {
log.Info("No evaluation data found in the override chain — running a fresh evaluate.")
return model.EvaluatePlan{Kind: model.EvaluatePlanReset, UpdateActions: plan.UpdateActions}, nil
}
return plan, nil
}

func (s *pushState) collectUserUpdatePlan() (model.EvaluatePlan, error) {
if len(s.inputs.updateParts) > 0 {
parsed, err := model.ParseUpdateActionsFromFlags(s.inputs.updateParts)
if err != nil {
return model.EvaluatePlan{}, err
}
plan := model.PlanFromUpdateActions(parsed)
if plan.Kind == model.EvaluatePlanUpdate && !s.canUpdateEvaluate() {
log.Info("No evaluation data found in the override chain — running a fresh evaluate.")
return model.EvaluatePlan{Kind: model.EvaluatePlanReset}, nil
}
return plan, nil
}
if !s.canUpdateEvaluate() {
log.Info("No evaluation data found in the override chain — running a fresh evaluate.")
return model.EvaluatePlan{Kind: model.EvaluatePlanReset}, nil
return model.PlanFromUpdateActions(parsed), nil
}
plan, err := model.AskForEvaluatePlan()
if err != nil {
Expand Down Expand Up @@ -448,17 +462,23 @@ func (s *pushState) sendSuccessEvent(codeResp *tensorleapapi.PushCodeSnapshotRes
analytics.SendEvent(analytics.EventCliProjectsPushSuccess, s.properties)
}

func (s *pushState) triggerEvaluate(versionId string, batchSize int, updateActions []tensorleapapi.UpdateAction, runUpdateEvaluate bool) error {
func (s *pushState) triggerEvaluate(versionId string, dispatch evalDispatch) error {
if dispatch.persistOnly {
if len(dispatch.updateActions) == 0 {
return nil
}
return model.PersistUpdateActions(s.ctx, s.projectId(), versionId, dispatch.updateActions)
}
if !s.inputs.runEval {
return nil
}
if runUpdateEvaluate {
if err := model.RunUpdateEvaluateArtifact(s.ctx, s.projectId(), versionId, updateActions); err != nil {
if dispatch.runUpdateEvaluate {
if err := model.RunUpdateEvaluateArtifact(s.ctx, s.projectId(), versionId, dispatch.updateActions); err != nil {
return fmt.Errorf("failed to run update evaluate: %w", err)
}
return nil
}
if err := model.RunEvaluate(s.ctx, s.projectId(), versionId, batchSize); err != nil {
if err := model.RunEvaluate(s.ctx, s.projectId(), versionId, dispatch.batchSize); err != nil {
return fmt.Errorf("failed to run evaluation: %w", err)
}
return nil
Expand Down
66 changes: 41 additions & 25 deletions docs/push-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,62 +47,77 @@ lists the candidate ids so you can re-run with the exact one.
leap push -o my-model -e
```

You'll get a multi-select prompt for what you changed (only edits the
engine can't auto-detect):
You'll get a multi-select prompt for what to update — engine no longer
auto-detects, so you pick what to refresh:

```
? What did you change in your code? (auto-detected: added meta/viz, metric-direction, insight-config)
? What do you want to update?
[Use arrows to move, space to select, type to filter]
[ ] Edited an existing metadata
[ ] Edited an existing visualization
[ ] Added or edited a metric
[ ] Force re-run insights
[ ] Metadata — full re-eval
[ ] Metric — full re-eval
[ ] Metric direction
[ ] Visualizations
[ ] Insights
```

Then a plan summary:

```
This will:
• Update visualizations
• Auto-detect added meta/viz, metric-direction, insight-config
• Regenerate visualizations
```

If you pick metadata or metric, the plan collapses to:

```
This will:
• Re-evaluate (full)
```

## Skip the prompt — name the artifacts explicitly

`--update` (`-u`) tells `leap push` exactly which artifacts to refresh.
`--update` (`-u`) tells `leap push` exactly what changed in the code.
It **implies `--eval`** — you no longer need both flags.

```bash
leap push -o my-model -u viz # regenerate visualizations only
leap push -o my-model -u metadata -u insights # refresh both
leap push -o my-model -u metric # full re-evaluation triggered by metric change
leap push -o my-model -u viz # regenerate visualizations only
leap push -o my-model -u metric_direction # cheap metric-direction patch
leap push -o my-model -u metric # triggers a full re-evaluation
leap push -o my-model -u metadata # triggers a full re-evaluation
leap push -o my-model -u visualization -u insights # refresh both
```

Accepted values (case-insensitive):

| short | full (still accepted) |
| -------------- | --------------------- |
| `metadata` | `update_metadata` |
| `metric` | `update_metric` |
| `insights` | `update_insights` |
| `visualization` / `viz` | `update_visualization` |
| short | full (still accepted) |
| ------------------------ | ------------------------- |
| `metadata` | `update_metadata` |
| `metric` | `update_metric` |
| `metric_direction` / `direction` | `update_metric_direction` |
| `insights` | `update_insights` |
| `visualization` / `viz` | `update_visualization` |

`metadata` and `metric` always trigger a fresh evaluation —
update_evaluate doesn't support them today.

## Overwrite without evaluating

```bash
leap push -o my-model # no --eval, no --update
```

When you overwrite without `--eval`, the CLI reminds you that
evaluation isn't automatic:
You still get the multi-select prompt — the answer is recorded on the
version so the next time someone (UI, another CLI run) runs an
evaluation against this version they see your declared intent.

```
NOTE: Overwriting replaces the version. Use the update-evaluate dialog
in the UI to re-evaluate (or re-run with --eval).
Recorded update actions on version (no evaluation dispatched).
```

The UI's update-evaluate dialog lets you drive the same diff-based
refresh interactively, post-push.
The UI's update-evaluate dialog uses the same five-action prompt as
the CLI's `--eval` flow.

## Deprecated flag still works

Expand All @@ -122,5 +137,6 @@ and proceeds normally. Update your scripts at your leisure.
| Overwrite by id | `leap push -o <id>` |
| Overwrite by name | `leap push -o <name>` |
| Overwrite + force-refresh viz | `leap push -o <name> -u viz` |
| Overwrite + refresh metadata and insights | `leap push -o <name> -u metadata -u insights` |
| Overwrite + full re-eval (auto-detect) | `leap push -o <name> -e` |
| Overwrite + cheap metric-direction patch | `leap push -o <name> -u metric_direction` |
| Overwrite + interactive prompt for what changed | `leap push -o <name> -e` |
| Overwrite + force full re-eval | `leap push -o <name> -u metric` |
2 changes: 1 addition & 1 deletion pkg/api/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ func HintVersionMismatch(err error) error {
return fmt.Errorf(
"%w\n\nThis may indicate a CLI/server version mismatch. "+
"Run 'leap server upgrade' on the server host to upgrade the server, "+
"or 'leap cli upgrade -s | bash' to upgrade the CLI.",
"or 'leap cli upgrade -s | bash' to upgrade the CLI",
err,
)
}
Expand Down
Loading
Loading