mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-09-20 10:05:57 +08:00
Cancel scheduled evaluation on error (#1688)
This commit is contained in:
parent
c29b6fdda2
commit
597d9fff3f
|
@ -280,6 +280,7 @@ const Session = {
|
|||
faviconForEvaluationStatus(evaluationStatus) {
|
||||
if (evaluationStatus === "evaluating") return "favicon-evaluating";
|
||||
if (evaluationStatus === "stale") return "favicon-stale";
|
||||
if (evaluationStatus === "errored") return "favicon-errored";
|
||||
return "favicon";
|
||||
},
|
||||
|
||||
|
|
|
@ -68,11 +68,8 @@ defmodule Livebook.LiveMarkdown.Export do
|
|||
end
|
||||
|
||||
defp notebook_metadata(notebook) do
|
||||
put_unless_default(
|
||||
%{},
|
||||
Map.take(notebook, [:persist_outputs, :autosave_interval_s]),
|
||||
Map.take(Notebook.new(), [:persist_outputs, :autosave_interval_s])
|
||||
)
|
||||
keys = [:persist_outputs, :autosave_interval_s]
|
||||
put_unless_default(%{}, Map.take(notebook, keys), Map.take(Notebook.new(), keys))
|
||||
end
|
||||
|
||||
defp render_section(section, notebook, ctx) do
|
||||
|
@ -148,11 +145,8 @@ defmodule Livebook.LiveMarkdown.Export do
|
|||
end
|
||||
|
||||
defp cell_metadata(%Cell.Code{} = cell) do
|
||||
put_unless_default(
|
||||
%{},
|
||||
Map.take(cell, [:disable_formatting, :reevaluate_automatically]),
|
||||
Map.take(Cell.Code.new(), [:disable_formatting, :reevaluate_automatically])
|
||||
)
|
||||
keys = [:disable_formatting, :reevaluate_automatically, :continue_on_error]
|
||||
put_unless_default(%{}, Map.take(cell, keys), Map.take(Cell.Code.new(), keys))
|
||||
end
|
||||
|
||||
defp cell_metadata(_cell), do: %{}
|
||||
|
|
|
@ -393,6 +393,9 @@ defmodule Livebook.LiveMarkdown.Import do
|
|||
{"reevaluate_automatically", reevaluate_automatically}, attrs ->
|
||||
Map.put(attrs, :reevaluate_automatically, reevaluate_automatically)
|
||||
|
||||
{"continue_on_error", continue_on_error}, attrs ->
|
||||
Map.put(attrs, :continue_on_error, continue_on_error)
|
||||
|
||||
_entry, attrs ->
|
||||
attrs
|
||||
end)
|
||||
|
|
|
@ -6,7 +6,14 @@ defmodule Livebook.Notebook.Cell.Code do
|
|||
# It consists of text content that the user can edit
|
||||
# and produces some output once evaluated.
|
||||
|
||||
defstruct [:id, :source, :outputs, :disable_formatting, :reevaluate_automatically]
|
||||
defstruct [
|
||||
:id,
|
||||
:source,
|
||||
:outputs,
|
||||
:disable_formatting,
|
||||
:reevaluate_automatically,
|
||||
:continue_on_error
|
||||
]
|
||||
|
||||
alias Livebook.Utils
|
||||
alias Livebook.Notebook.Cell
|
||||
|
@ -16,7 +23,8 @@ defmodule Livebook.Notebook.Cell.Code do
|
|||
source: String.t() | :__pruned__,
|
||||
outputs: list(Cell.indexed_output()),
|
||||
disable_formatting: boolean(),
|
||||
reevaluate_automatically: boolean()
|
||||
reevaluate_automatically: boolean(),
|
||||
continue_on_error: boolean()
|
||||
}
|
||||
|
||||
@doc """
|
||||
|
@ -29,7 +37,8 @@ defmodule Livebook.Notebook.Cell.Code do
|
|||
source: "",
|
||||
outputs: [],
|
||||
disable_formatting: false,
|
||||
reevaluate_automatically: false
|
||||
reevaluate_automatically: false,
|
||||
continue_on_error: false
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,6 +78,7 @@ defprotocol Livebook.Runtime do
|
|||
dependencies between evaluations and avoids unnecessary reevaluations.
|
||||
"""
|
||||
@type evaluation_response_metadata :: %{
|
||||
errored: boolean(),
|
||||
evaluation_time_ms: non_neg_integer(),
|
||||
code_error: code_error(),
|
||||
memory_usage: runtime_memory(),
|
||||
|
|
|
@ -466,6 +466,7 @@ defmodule Livebook.Runtime.Evaluator do
|
|||
output = state.formatter.format_result(result)
|
||||
|
||||
metadata = %{
|
||||
errored: elem(result, 0) == :error,
|
||||
evaluation_time_ms: evaluation_time_ms,
|
||||
memory_usage: memory(),
|
||||
code_error: code_error,
|
||||
|
|
|
@ -88,11 +88,13 @@ defmodule Livebook.Session.Data do
|
|||
@type cell_eval_info :: %{
|
||||
validity: cell_evaluation_validity(),
|
||||
status: cell_evaluation_status(),
|
||||
errored: boolean(),
|
||||
snapshot: snapshot(),
|
||||
evaluation_digest: String.t() | nil,
|
||||
evaluation_snapshot: snapshot() | nil,
|
||||
evaluation_time_ms: integer() | nil,
|
||||
evaluation_start: DateTime.t() | nil,
|
||||
evaluation_end: DateTime.t() | nil,
|
||||
evaluation_number: non_neg_integer(),
|
||||
outputs_batch_number: non_neg_integer(),
|
||||
bound_to_input_ids: MapSet.t(input_id()),
|
||||
|
@ -1051,19 +1053,29 @@ defmodule Livebook.Session.Data do
|
|||
end
|
||||
|
||||
defp finish_cell_evaluation(data_actions, cell, section, metadata) do
|
||||
data_actions =
|
||||
data_actions
|
||||
|> update_cell_eval_info!(cell.id, fn eval_info ->
|
||||
%{
|
||||
eval_info
|
||||
| status: :ready,
|
||||
errored: metadata.errored,
|
||||
evaluation_time_ms: metadata.evaluation_time_ms,
|
||||
identifiers_used: metadata.identifiers_used,
|
||||
identifiers_defined: metadata.identifiers_defined,
|
||||
bound_to_input_ids: eval_info.new_bound_to_input_ids
|
||||
bound_to_input_ids: eval_info.new_bound_to_input_ids,
|
||||
evaluation_end: DateTime.utc_now()
|
||||
}
|
||||
end)
|
||||
|> update_cell_evaluation_snapshot(cell, section)
|
||||
|> set_section_info!(section.id, evaluating_cell_id: nil)
|
||||
|
||||
if metadata.errored and not Map.get(cell, :continue_on_error, false) do
|
||||
data_actions
|
||||
|> unqueue_child_cells_evaluation(cell)
|
||||
else
|
||||
data_actions
|
||||
end
|
||||
end
|
||||
|
||||
defp update_cell_evaluation_snapshot({data, _} = data_actions, cell, section) do
|
||||
|
@ -1241,7 +1253,8 @@ defmodule Livebook.Session.Data do
|
|||
data: data,
|
||||
# This is a rough estimate, the exact time is measured in the
|
||||
# evaluator itself
|
||||
evaluation_start: DateTime.utc_now()
|
||||
evaluation_start: DateTime.utc_now(),
|
||||
evaluation_end: nil
|
||||
}
|
||||
end)
|
||||
|> set_section_info!(section.id,
|
||||
|
@ -1786,9 +1799,11 @@ defmodule Livebook.Session.Data do
|
|||
%{
|
||||
validity: :fresh,
|
||||
status: :ready,
|
||||
errored: false,
|
||||
evaluation_digest: nil,
|
||||
evaluation_time_ms: nil,
|
||||
evaluation_start: nil,
|
||||
evaluation_end: nil,
|
||||
evaluation_number: 0,
|
||||
outputs_batch_number: 0,
|
||||
bound_to_input_ids: MapSet.new(),
|
||||
|
@ -1797,7 +1812,8 @@ defmodule Livebook.Session.Data do
|
|||
identifiers_defined: %{},
|
||||
snapshot: nil,
|
||||
evaluation_snapshot: nil,
|
||||
data: nil
|
||||
data: nil,
|
||||
reevaluates_automatically: false
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -1923,6 +1939,7 @@ defmodule Livebook.Session.Data do
|
|||
data_actions
|
||||
|> compute_snapshots()
|
||||
|> update_validity()
|
||||
|> update_reevaluates_automatically()
|
||||
# After updating validity there may be new stale cells, so we check
|
||||
# if any of them is configured for automatic reevaluation
|
||||
|> maybe_queue_reevaluating_cells()
|
||||
|
@ -2066,15 +2083,45 @@ defmodule Livebook.Session.Data do
|
|||
end)
|
||||
end
|
||||
|
||||
defp update_reevaluates_automatically({data, _} = data_actions) do
|
||||
eval_parents = cell_evaluation_parents(data)
|
||||
|
||||
cells_with_section = Notebook.evaluable_cells_with_section(data.notebook)
|
||||
cell_lookup = for {cell, _section} <- cells_with_section, into: %{}, do: {cell.id, cell}
|
||||
|
||||
reduce(data_actions, cells_with_section, fn data_actions, {cell, _section} ->
|
||||
info = data.cell_infos[cell.id]
|
||||
|
||||
# Note that we disable automatic reevaluation if any evaluation
|
||||
# parent errored (unless continue-on-error is enabled)
|
||||
reevaluates_automatically =
|
||||
Map.get(cell, :reevaluate_automatically, false) and
|
||||
info.eval.validity in [:evaluated, :stale] and
|
||||
not Enum.any?(eval_parents[cell.id], fn parent_id ->
|
||||
errored? = data.cell_infos[parent_id].eval.errored
|
||||
continue_on_error? = Map.get(cell_lookup[parent_id], :continue_on_error, false)
|
||||
errored? and not continue_on_error?
|
||||
end)
|
||||
|
||||
if reevaluates_automatically == info.eval.reevaluates_automatically do
|
||||
data_actions
|
||||
else
|
||||
update_cell_eval_info!(
|
||||
data_actions,
|
||||
cell.id,
|
||||
&%{&1 | reevaluates_automatically: reevaluates_automatically}
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp maybe_queue_reevaluating_cells({data, _} = data_actions) do
|
||||
cells_to_reevaluate =
|
||||
data.notebook
|
||||
|> Notebook.evaluable_cells_with_section()
|
||||
|> Enum.filter(fn {cell, _section} ->
|
||||
info = data.cell_infos[cell.id]
|
||||
|
||||
info.eval.status == :ready and info.eval.validity == :stale and
|
||||
Map.get(cell, :reevaluate_automatically, false)
|
||||
match?(%{status: :ready, validity: :stale, reevaluates_automatically: true}, info.eval)
|
||||
end)
|
||||
|
||||
data_actions
|
||||
|
|
|
@ -2031,15 +2031,29 @@ defmodule LivebookWeb.SessionLive do
|
|||
end
|
||||
|
||||
defp cells_status(cells, data) do
|
||||
eval_infos =
|
||||
for cell <- cells,
|
||||
Cell.evaluable?(cell),
|
||||
info = data.cell_infos[cell.id].eval,
|
||||
do: Map.put(info, :id, cell.id)
|
||||
|
||||
most_recent =
|
||||
eval_infos
|
||||
|> Enum.filter(& &1.evaluation_end)
|
||||
|> Enum.max_by(& &1.evaluation_end, DateTime, fn -> nil end)
|
||||
|
||||
cond do
|
||||
evaluating = Enum.find(cells, &evaluating?(&1, data)) ->
|
||||
evaluating = Enum.find(eval_infos, &(&1.status == :evaluating)) ->
|
||||
{:evaluating, evaluating.id}
|
||||
|
||||
stale = Enum.find(cells, &stale?(&1, data)) ->
|
||||
most_recent != nil and most_recent.errored ->
|
||||
{:errored, most_recent.id}
|
||||
|
||||
stale = Enum.find(eval_infos, &(&1.validity == :stale)) ->
|
||||
{:stale, stale.id}
|
||||
|
||||
evaluated = Enum.find(Enum.reverse(cells), &evaluated?(&1, data)) ->
|
||||
{:evaluated, evaluated.id}
|
||||
most_recent != nil ->
|
||||
{:evaluated, most_recent.id}
|
||||
|
||||
true ->
|
||||
{:fresh, nil}
|
||||
|
@ -2055,18 +2069,6 @@ defmodule LivebookWeb.SessionLive do
|
|||
cells_status(cells, data)
|
||||
end
|
||||
|
||||
defp evaluating?(cell, data) do
|
||||
get_in(data.cell_infos, [cell.id, :eval, :status]) == :evaluating
|
||||
end
|
||||
|
||||
defp stale?(cell, data) do
|
||||
get_in(data.cell_infos, [cell.id, :eval, :validity]) == :stale
|
||||
end
|
||||
|
||||
defp evaluated?(cell, data) do
|
||||
get_in(data.cell_infos, [cell.id, :eval, :validity]) == :evaluated
|
||||
end
|
||||
|
||||
defp section_views(sections, data) do
|
||||
sections
|
||||
|> Enum.map(& &1.name)
|
||||
|
@ -2142,6 +2144,8 @@ defmodule LivebookWeb.SessionLive do
|
|||
outputs: cell.outputs,
|
||||
validity: eval_info.validity,
|
||||
status: eval_info.status,
|
||||
errored: eval_info.errored,
|
||||
reevaluates_automatically: eval_info.reevaluates_automatically,
|
||||
evaluation_time_ms: eval_info.evaluation_time_ms,
|
||||
evaluation_start: eval_info.evaluation_start,
|
||||
evaluation_digest: encode_digest(eval_info.evaluation_digest),
|
||||
|
|
|
@ -68,6 +68,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
|||
validity={@cell_view.eval.validity}
|
||||
status={@cell_view.eval.status}
|
||||
reevaluate_automatically={@cell_view.reevaluate_automatically}
|
||||
reevaluates_automatically={@cell_view.eval.reevaluates_automatically}
|
||||
/>
|
||||
</:primary>
|
||||
<:secondary>
|
||||
|
@ -167,6 +168,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
|||
validity={@cell_view.eval.validity}
|
||||
status={@cell_view.eval.status}
|
||||
reevaluate_automatically={false}
|
||||
reevaluates_automatically={@cell_view.eval.reevaluates_automatically}
|
||||
/>
|
||||
</:primary>
|
||||
<:secondary>
|
||||
|
@ -307,7 +309,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
|||
data-cell-id={@cell_id}
|
||||
>
|
||||
<%= cond do %>
|
||||
<% @reevaluate_automatically and @validity in [:evaluated, :stale] -> %>
|
||||
<% @reevaluates_automatically -> %>
|
||||
<.remix_icon icon="check-line" class="text-xl" />
|
||||
<span class="text-sm font-medium">Reevaluates automatically</span>
|
||||
<% @validity == :evaluated -> %>
|
||||
|
@ -653,7 +655,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
|||
defp cell_status(%{cell_view: %{eval: %{validity: :evaluated}}} = assigns) do
|
||||
~H"""
|
||||
<.status_indicator
|
||||
circle_class="bg-green-bright-400"
|
||||
circle_class={if(@cell_view.eval.errored, do: "bg-red-400", else: "bg-green-bright-400")}
|
||||
change_indicator={true}
|
||||
tooltip={evaluated_label(@cell_view.eval.evaluation_time_ms)}
|
||||
>
|
||||
|
|
|
@ -12,6 +12,7 @@ defmodule LivebookWeb.SessionLive.CodeCellSettingsComponent do
|
|||
|> assign(assigns)
|
||||
|> assign_new(:disable_formatting, fn -> cell.disable_formatting end)
|
||||
|> assign_new(:reevaluate_automatically, fn -> cell.reevaluate_automatically end)
|
||||
|> assign_new(:continue_on_error, fn -> cell.continue_on_error end)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
@ -38,6 +39,13 @@ defmodule LivebookWeb.SessionLive.CodeCellSettingsComponent do
|
|||
checked={@reevaluate_automatically}
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full flex-col space-y-6 mt-4">
|
||||
<.switch_checkbox
|
||||
name="continue_on_error"
|
||||
label="Continue on error"
|
||||
checked={@continue_on_error}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-8 flex justify-begin space-x-2">
|
||||
<button class="button-base button-blue" type="submit">
|
||||
Save
|
||||
|
@ -54,16 +62,19 @@ defmodule LivebookWeb.SessionLive.CodeCellSettingsComponent do
|
|||
"save",
|
||||
%{
|
||||
"enable_formatting" => enable_formatting,
|
||||
"reevaluate_automatically" => reevaluate_automatically
|
||||
"reevaluate_automatically" => reevaluate_automatically,
|
||||
"continue_on_error" => continue_on_error
|
||||
},
|
||||
socket
|
||||
) do
|
||||
disable_formatting = enable_formatting == "false"
|
||||
reevaluate_automatically = reevaluate_automatically == "true"
|
||||
continue_on_error = continue_on_error == "true"
|
||||
|
||||
Session.set_cell_attributes(socket.assigns.session.pid, socket.assigns.cell.id, %{
|
||||
disable_formatting: disable_formatting,
|
||||
reevaluate_automatically: reevaluate_automatically
|
||||
reevaluate_automatically: reevaluate_automatically,
|
||||
continue_on_error: continue_on_error
|
||||
})
|
||||
|
||||
{:noreply, push_patch(socket, to: socket.assigns.return_to)}
|
||||
|
|
|
@ -190,6 +190,21 @@ defmodule LivebookWeb.SessionLive.IndicatorsComponent do
|
|||
"""
|
||||
end
|
||||
|
||||
defp global_status(%{status: :errored} = assigns) do
|
||||
~H"""
|
||||
<span class="tooltip left" data-tooltip="Go to last evaluated cell">
|
||||
<button
|
||||
class="border-red-300 icon-button icon-outlined-button hover:bg-red-50 focus:bg-red-50"
|
||||
aria-label="go to last evaluated cell"
|
||||
data-el-focus-cell-button
|
||||
data-target={@cell_id}
|
||||
>
|
||||
<.remix_icon icon="loader-3-line" class="text-xl text-red-400" />
|
||||
</button>
|
||||
</span>
|
||||
"""
|
||||
end
|
||||
|
||||
defp global_status(%{status: :stale} = assigns) do
|
||||
~H"""
|
||||
<span class="tooltip left" data-tooltip="Go to first stale cell">
|
||||
|
|
87
static/favicon-errored.svg
Normal file
87
static/favicon-errored.svg
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg version="1.1" id="svg2" xml:space="preserve" width="823.87854" height="901.65137" viewBox="0 0 823.87854 901.65133" sodipodi:docname="favicon-errored.svg" inkscape:version="1.2.2 (b0a84865, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>
|
||||
image/svg+xml
|
||||
</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs id="defs6">
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath20">
|
||||
<path d="M 0,805.333 H 3800 V 0 H 0 Z" id="path18"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath148">
|
||||
<path d="M 353.619,544.464 H 573.136 V 440.053 H 353.619 Z" id="path146"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath172">
|
||||
<path d="m 351.9,352.883 h 95.147 V 122.614 H 351.9 Z" id="path170"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath1292">
|
||||
<path id="path1294" style="fill:#000000;fill-opacity:1;stroke-width:7.50045;stroke-linecap:round;stroke-linejoin:round" d="m 202.05067,751.83844 c -9.972,0 -18,-8.028 -18,-18 V -10.124963 c 0,-9.971978 8.028,-17.999978 18,-17.999978 h 710.98537 c 9.972,0 18,8.028 18,17.999978 V 733.83844 c 0,9.972 -8.028,18 -18,18 z M 696.25088,372.24907 a 187.35425,187.35425 0 0 0 0.2417,0 A 187.35425,187.35425 0 0 0 883.84609,184.89408 187.35425,187.35425 0 0 0 696.49258,-2.4594376 187.35425,187.35425 0 0 0 509.1376,184.89408 187.35425,187.35425 0 0 0 696.25088,372.24907 Z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="1027" id="namedview4" showgrid="false" inkscape:zoom="0.48408519" inkscape:cx="480.28736" inkscape:cy="837.66248" inkscape:window-x="584" inkscape:window-y="25" inkscape:window-maximized="0" inkscape:current-layer="g10" inkscape:document-rotation="0" inkscape:showpageshadow="2" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1"/>
|
||||
<g id="g10" inkscape:groupmode="layer" inkscape:label="editable_Livebook_Logo" transform="matrix(1.3333333,0,0,-1.3333333,-271.62159,986.33208)">
|
||||
<g id="g14" clip-path="url(#clipPath1292)">
|
||||
<g id="g16" clip-path="url(#clipPath20)">
|
||||
<g id="g118" transform="translate(425.3255,680.1453)">
|
||||
<path d="M 0,0 C 27.577,33.093 44.051,35.111 44.303,34.988 53.563,33.872 54.75,25.563 54.75,25.563 L 48.815,7.759 36.397,-25.372 c -12.991,2.166 -50.193,4.688 -52.949,3.31 z" style="fill:#825161;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path120"/>
|
||||
</g>
|
||||
<g id="g122" transform="translate(465.032,662.4954)">
|
||||
<path d="m 0,0 c 0,0 1.103,3.31 3.309,9.928 0.149,0.446 -33.093,5.515 -55.154,-5.515 0,0 33.092,2.206 51.845,-4.413" style="fill:#563642;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path124"/>
|
||||
</g>
|
||||
<g id="g126" transform="translate(416.4956,669.1141)">
|
||||
<path d="m 0,0 5.638,59.34 c -1.225,10.155 -7.844,14.568 -18.421,8.598 -1.514,-0.854 -3.384,-2.171 -4.941,-3.407 -37.828,-30.007 -44.168,-81.382 -45.215,-106.367 0.017,-0.925 -0.012,-5.787 -0.051,-6.7 -0.989,-51.846 68.077,-90.357 87.258,-109.207 -3.976,7.409 -33.71,32.402 -37.505,50.743 -2.915,14.088 0.033,25.5 4.619,34.123 5.83,10.959 15.579,19.332 27.085,23.993 29.794,12.071 59.031,10.045 84.121,5.863 38.691,-6.448 69.005,-22.321 70.537,-23.132 0.041,-0.022 0.061,-0.033 0.061,-0.033 -0.028,0.014 1.707,6.516 0,9.928 -2.206,4.413 -7.109,6.373 -7.868,6.778 C 153.087,-42.987 98.646,-13.885 51.183,-3.768 54.052,-4.412 25.371,2.207 0,0" style="fill:#825161;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path128"/>
|
||||
</g>
|
||||
<g id="g130" transform="translate(525.7024,616.1653)">
|
||||
<path d="m 0,0 c -25.09,4.182 -50.834,4.135 -80.628,-7.936 -11.505,-4.661 -21.747,-14.412 -27.577,-25.371 -4.587,-8.622 -6.517,-17.435 -3.208,-33.982 2.632,-13.159 8.825,-26.474 14.341,-35.299 22.621,-18.788 30.59,-16.362 56.257,-31.99 -7.706,37.317 0.852,89.735 89.315,110.745 0.087,0.021 7.234,1.567 7.516,1.687 3.934,1.668 6.669,5.099 7.922,8.931 C 63.067,-12.751 39.14,-6.523 0,0" style="fill:#c4adb5;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path132"/>
|
||||
</g>
|
||||
<g id="g134" transform="translate(589.6821,607.3406)">
|
||||
<path d="m 0,0 c -13.237,4.413 -31.99,9.928 -57.708,16.292 -29.298,7.249 -64.537,10.376 -90.107,0.254 0,0 -58.464,-16.546 -38.609,-77.216 l 3.31,-13.237 c 0,0 6.581,0.003 13.812,-5.112 -1.147,3.495 -5.422,17.002 -6.132,20.567 -3.298,16.556 0.033,25.515 4.604,34.142 5.81,10.966 15.527,19.344 26.994,24.008 6.942,2.824 13.999,4.993 21.056,6.608 4.301,0.984 8.665,1.64 13.064,1.989 21.894,1.741 50.539,1.716 107.51,-12.707 l 0.154,0.464 c 0.948,-0.273 1.421,-0.421 1.421,-0.421 C -0.589,-4.391 0.177,-0.059 0,0" style="fill:#4c313b;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path136"/>
|
||||
</g>
|
||||
<g id="g138" transform="translate(354.1892,303.2845)">
|
||||
<path d="m 0,0 c -0.044,-10.08 2.465,-20.009 7.354,-28.825 9.226,-16.633 29.838,-42.629 75.711,-72.288 52.438,-33.904 146.912,-63.851 146.912,-63.851 l 13.238,291.422 c 0,0 -243.836,115.46 -243.836,197.646 C -0.621,326.15 0.245,55.466 0,0" style="fill:#dbd8d5;fill-opacity:1;fill-rule:evenodd;stroke:none" id="path140"/>
|
||||
</g>
|
||||
<g id="g142">
|
||||
<g id="g144"/>
|
||||
<g id="g156">
|
||||
<g clip-path="url(#clipPath148)" opacity="0.25" id="g154">
|
||||
<g transform="translate(359.1348,529.0208)" id="g152">
|
||||
<path d="m 0,0 214.001,-88.248 v 1.103 L -5.516,15.443 V 2.206 c 0,0 0.102,-0.038 5.516,-2.206" style="fill:#36272b;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path150"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g158" transform="translate(293.3445,165.7054)">
|
||||
<path d="m 0,0 c -3.146,1.255 -5.206,4.305 -5.196,7.692 l 0.424,376.462 c -0.11,2.985 0.066,7.804 5.48,5.636 L 262.142,286.099 c 0,-3.309 0.015,-2.127 0,-5.516 l -0.648,-371.921 c -0.021,-4.584 0.648,-7.544 0.648,-10.854 C 262.142,-102.82 0,0 0,0" style="fill:#c24063;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path160"/>
|
||||
</g>
|
||||
<g id="g162" transform="translate(555.4861,63.5135)">
|
||||
<path d="m 0,0 260.943,102.192 c 3.146,1.255 5.206,4.305 5.196,7.692 l -0.424,376.462 c 0.061,1.642 -0.271,3.506 -1.483,4.489 -0.99,0.804 -2.569,1.019 -5.004,0.043 L 0,388.291 c 0,-4.413 -0.015,-2.127 0,-5.516 L -0.551,10.854 C -0.525,5.032 0,3.31 0,0" style="fill:#ff87a7;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path164"/>
|
||||
</g>
|
||||
<g id="g166">
|
||||
<g id="g168"/>
|
||||
<g id="g180">
|
||||
<g clip-path="url(#clipPath172)" opacity="0.25" id="g178">
|
||||
<g transform="translate(396.6399,125.2874)" id="g176">
|
||||
<path d="m 0,0 c 0,0 25.629,-13.707 41.918,20.958 16.816,35.79 11.031,138.991 -39.073,206.638 0,0 33.557,-168.029 -47.585,-209.748 z" style="fill:#36272b;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path174"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g182" transform="translate(235.1873,329.8465)">
|
||||
<path d="m 0,0 -6.929,-91.382 c -1.889,-24.9 6.023,-49.032 22.274,-67.955 16.147,-18.923 39.281,-30.415 63.952,-32.286 5.208,-0.395 10.484,-0.359 15.781,0.124 44.579,4.068 80.894,42.703 84.471,89.866 l 3.337,44.017 C 187.631,4.953 156.231,63.07 102.889,90.441 59.026,112.964 14.225,107.04 -30.368,72.879 -9.406,54.897 2.1,27.688 0,0" style="fill:#1f2728;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path184"/>
|
||||
</g>
|
||||
<g id="g186" transform="translate(311.2022,170.5091)">
|
||||
<path d="m 0,0 c -16.25,18.924 -24.162,43.056 -22.274,67.956 l 6.93,91.381 c 2.099,27.689 -9.406,54.898 -30.368,72.879 16.567,12.692 33.162,21.478 49.728,26.364 -36.867,10.985 -74.257,2.169 -111.502,-26.364 20.962,-17.981 32.468,-45.19 30.368,-72.879 l -6.929,-91.381 c -1.889,-24.9 6.023,-49.032 22.274,-67.956 16.147,-18.922 39.281,-30.415 63.952,-32.286 5.208,-0.395 10.484,-0.359 15.781,0.125 7.571,0.691 14.895,2.4 21.87,4.958 C 24.539,-21.832 10.724,-12.567 0,0" style="fill:#2f393d;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path188"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<circle style="fill:#e97579;fill-opacity:1;stroke-width:10.4152;stroke-linecap:round;stroke-linejoin:round" id="path1157" cx="698.31262" cy="-185.48764" transform="scale(1,-1)" r="114.5602"/>
|
||||
<path fill="none" d="m -530.12015,778.48595 h 18 v -18 h -18 z" id="path2" style="stroke-width:0.75"/>
|
||||
<path d="m -522.62015,767.10695 6.894,6.89475 1.06125,-1.0605 -7.95525,-7.95525 -4.773,4.773 1.0605,1.0605 z" id="path4" style="stroke-width:0.75"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.1 KiB |
|
@ -29,6 +29,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
|
|||
Notebook.Cell.new(:code)
|
||||
| disable_formatting: true,
|
||||
reevaluate_automatically: true,
|
||||
continue_on_error: true,
|
||||
source: """
|
||||
Enum.to_list(1..10)\
|
||||
"""
|
||||
|
@ -101,7 +102,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
|
|||
|
||||
$x_{i} + y_{i}$
|
||||
|
||||
<!-- livebook:{"disable_formatting":true,"reevaluate_automatically":true} -->
|
||||
<!-- livebook:{"continue_on_error":true,"disable_formatting":true,"reevaluate_automatically":true} -->
|
||||
|
||||
```elixir
|
||||
Enum.to_list(1..10)
|
||||
|
|
|
@ -19,7 +19,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
|
|||
|
||||
$x_{i} + y_{i}$
|
||||
|
||||
<!-- livebook:{"disable_formatting":true,"reevaluate_automatically":true} -->
|
||||
<!-- livebook:{"continue_on_error":true,"disable_formatting":true,"reevaluate_automatically":true} -->
|
||||
|
||||
```elixir
|
||||
Enum.to_list(1..10)
|
||||
|
@ -80,6 +80,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
|
|||
%Cell.Code{
|
||||
disable_formatting: true,
|
||||
reevaluate_automatically: true,
|
||||
continue_on_error: true,
|
||||
source: """
|
||||
Enum.to_list(1..10)\
|
||||
"""
|
||||
|
|
|
@ -15,7 +15,14 @@ defmodule Livebook.Session.DataTest do
|
|||
defp eval_meta(opts \\ []) do
|
||||
uses = opts[:uses] || []
|
||||
defines = opts[:defines] || %{}
|
||||
%{evaluation_time_ms: 10, identifiers_used: uses, identifiers_defined: defines}
|
||||
errored = Keyword.get(opts, :errored, false)
|
||||
|
||||
%{
|
||||
errored: errored,
|
||||
evaluation_time_ms: 10,
|
||||
identifiers_used: uses,
|
||||
identifiers_defined: defines
|
||||
}
|
||||
end
|
||||
|
||||
describe "new/1" do
|
||||
|
@ -2038,8 +2045,7 @@ defmodule Livebook.Session.DataTest do
|
|||
])
|
||||
|
||||
operation =
|
||||
{:add_cell_evaluation_response, @cid, "c1", @eval_resp,
|
||||
eval_meta(%{defines: %{"c1" => 1}})}
|
||||
{:add_cell_evaluation_response, @cid, "c1", @eval_resp, eval_meta(defines: %{"c1" => 1})}
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
|
@ -2173,6 +2179,98 @@ defmodule Livebook.Session.DataTest do
|
|||
}, _actions} = Data.apply_operation(data, operation)
|
||||
end
|
||||
|
||||
test "unqueues child cells if the evaluation errored" do
|
||||
data =
|
||||
data_after_operations!([
|
||||
{:insert_section, @cid, 0, "s1"},
|
||||
{:insert_cell, @cid, "s1", 0, :code, "c1", %{}},
|
||||
{:insert_cell, @cid, "s1", 1, :code, "c2", %{}},
|
||||
{:insert_section, @cid, 1, "s2"},
|
||||
{:insert_cell, @cid, "s2", 0, :code, "c3", %{}},
|
||||
{:insert_cell, @cid, "s2", 1, :code, "c4", %{}},
|
||||
{:set_runtime, @cid, connected_noop_runtime()},
|
||||
evaluate_cells_operations(["setup"]),
|
||||
{:queue_cells_evaluation, @cid, ["c1", "c2", "c3", "c4"]}
|
||||
])
|
||||
|
||||
operation =
|
||||
{:add_cell_evaluation_response, @cid, "c1", @eval_resp, eval_meta(errored: true)}
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
cell_infos: %{
|
||||
"c2" => %{eval: %{status: :ready, validity: :fresh}},
|
||||
"c3" => %{eval: %{status: :ready, validity: :fresh}},
|
||||
"c4" => %{eval: %{status: :ready, validity: :fresh}}
|
||||
},
|
||||
section_infos: %{
|
||||
"s1" => %{evaluating_cell_id: nil},
|
||||
"s2" => %{evaluating_cell_id: nil}
|
||||
}
|
||||
}, []} = Data.apply_operation(data, operation)
|
||||
end
|
||||
|
||||
test "disables child cells automatic reevaluation if the evaluation errored" do
|
||||
data =
|
||||
data_after_operations!([
|
||||
{:insert_section, @cid, 0, "s1"},
|
||||
{:insert_cell, @cid, "s1", 0, :code, "c1", %{}},
|
||||
{:insert_cell, @cid, "s1", 1, :code, "c2", %{}},
|
||||
{:insert_cell, @cid, "s1", 2, :code, "c3", %{}},
|
||||
{:set_cell_attributes, @cid, "c3", %{reevaluate_automatically: true}},
|
||||
{:set_runtime, @cid, connected_noop_runtime()},
|
||||
evaluate_cells_operations(["setup", "c1", "c2", "c3"],
|
||||
uses: %{"c2" => ["c1"], "c3" => ["c2"]}
|
||||
),
|
||||
{:queue_cells_evaluation, @cid, ["c1"]}
|
||||
])
|
||||
|
||||
operation =
|
||||
{:add_cell_evaluation_response, @cid, "c1", @eval_resp, eval_meta(errored: true)}
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
cell_infos: %{
|
||||
"c2" => %{eval: %{status: :ready}},
|
||||
"c3" => %{eval: %{status: :ready, reevaluates_automatically: false}}
|
||||
},
|
||||
section_infos: %{
|
||||
"s1" => %{evaluating_cell_id: nil}
|
||||
}
|
||||
}, []} = Data.apply_operation(data, operation)
|
||||
end
|
||||
|
||||
test "re-enables child cells automatic reevaluation if errored evaluation is fixed" do
|
||||
data =
|
||||
data_after_operations!([
|
||||
{:insert_section, @cid, 0, "s1"},
|
||||
{:insert_cell, @cid, "s1", 0, :code, "c1", %{}},
|
||||
{:insert_cell, @cid, "s1", 1, :code, "c2", %{}},
|
||||
{:insert_cell, @cid, "s1", 2, :code, "c3", %{}},
|
||||
{:set_cell_attributes, @cid, "c3", %{reevaluate_automatically: true}},
|
||||
{:set_runtime, @cid, connected_noop_runtime()},
|
||||
evaluate_cells_operations(["setup", "c1", "c2", "c3"],
|
||||
uses: %{"c2" => ["c1"], "c3" => ["c2"]}
|
||||
),
|
||||
{:queue_cells_evaluation, @cid, ["c1"]},
|
||||
{:add_cell_evaluation_response, @cid, "c1", @eval_resp, eval_meta(errored: true)},
|
||||
{:queue_cells_evaluation, @cid, ["c1"]}
|
||||
])
|
||||
|
||||
operation = {:add_cell_evaluation_response, @cid, "c1", @eval_resp, eval_meta()}
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
cell_infos: %{
|
||||
"c2" => %{eval: %{status: :evaluating}},
|
||||
"c3" => %{eval: %{status: :queued, reevaluates_automatically: true}}
|
||||
},
|
||||
section_infos: %{
|
||||
"s1" => %{evaluating_cell_id: "c2"}
|
||||
}
|
||||
}, _actions} = Data.apply_operation(data, operation)
|
||||
end
|
||||
|
||||
test "if bound input value changes during cell evaluation, the cell is marked as stale afterwards" do
|
||||
input = %{id: "i1", type: :text, label: "Text", default: "hey"}
|
||||
|
||||
|
|
|
@ -7,7 +7,12 @@ defmodule Livebook.SessionTest do
|
|||
alias Livebook.Notebook.{Section, Cell}
|
||||
alias Livebook.Session.Data
|
||||
|
||||
@eval_meta %{evaluation_time_ms: 10, identifiers_used: [], identifiers_defined: %{}}
|
||||
@eval_meta %{
|
||||
errored: false,
|
||||
evaluation_time_ms: 10,
|
||||
identifiers_used: [],
|
||||
identifiers_defined: %{}
|
||||
}
|
||||
|
||||
setup do
|
||||
session = start_session()
|
||||
|
|
Loading…
Reference in a new issue