improvement: support depending on requests that will be added

docs: improve flow docs

closes: #418
improvement: support dynamic action steps in `Ash.Flow`
This commit is contained in:
Zach Daniel 2022-10-16 14:07:57 -05:00
parent 652342c41d
commit 6751afc683
7 changed files with 482 additions and 289 deletions

View file

@ -65,6 +65,16 @@ end
- `%{step_name: :key}` will return a map of each key to the provided step name, i.e `%{key: <step_name_result>}`
- `[:step_name]` - which is equivalent to `%{step_name: :step_name}`
A flow always returns an `%Ash.Flow.Result{}`, and the return value of a successful flow will be available in `%Ash.Flow.Result{result: result}` when the flow did not encounter an error.
If the flow resulted in an error, `error?` is set to `true`, and the result will be `nil`.
## Halting and Resuming Flows
A flow can be halted by using the `halt_if` option on a step, or by a custom step returning `{:error, Ash.Flow.Error.Halted.exception(reason: reason)}`
In this case, the flow will be marked as `complete?: false`. The result of each step up until this point is saved, and you can then rerun the flow with different inputs by passing the incomplete result into the `resume` option when running the flow again. Individual steps can be rerun by deleting them from the `data` field of the flow.
## Errors
Currently, any error anywhere in the flow will fail the flow and will return an error. Over time, error handling behavior will be added, as well as the ability to customize how transactions are rolled back, and to handle errors in a custom way.

View file

@ -61,6 +61,7 @@ defmodule Ash.Engine do
opts: [],
requests: [],
data: %{},
dependencies_waiting_on_request: MapSet.new(),
unsent_dependencies: [],
dependencies_seen: MapSet.new(),
dependencies: %{},
@ -466,7 +467,6 @@ defmodule Ash.Engine do
end
defp do_run_iteration(state, request) do
log(state, fn -> breakdown(state) end)
{state, notifications, dependencies} = fully_advance_request(state, request)
state
@ -516,13 +516,6 @@ defmodule Ash.Engine do
end
end
defp breakdown(state) do
"""
State breakdown:
#{Enum.map_join(state.requests, "\n", &"#{&1.name}: #{&1.state}")}
"""
end
def long_breakdown(state) do
"""
#{errors(state)}
@ -724,32 +717,36 @@ defmodule Ash.Engine do
depended_on_request = Enum.find(state.requests, &(&1.path == dep_path))
if !depended_on_request do
raise "Engine Error in request #{inspect(request_path)}: No request found with path #{inspect(dep_path)}. Available paths:\n #{Enum.map_join(state.requests, "\n", &inspect(&1.path))}"
if depended_on_request do
# we want to send things from non async requests
# after we've sent all info to async requests
unsent_dependencies =
if depended_on_request && depended_on_request.async? do
state.unsent_dependencies ++ [{request_path, dep}]
else
[{request_path, dep} | state.unsent_dependencies]
end
%{
state
| dependencies:
state.dependencies
|> Map.put_new_lazy(request_path, fn -> MapSet.new() end)
|> Map.update!(request_path, &MapSet.put(&1, {dep_path, dep_field})),
reverse_dependencies:
state.reverse_dependencies
|> Map.put_new_lazy(dep_path, fn -> MapSet.new() end)
|> Map.update!(dep_path, &MapSet.put(&1, {request_path, dep_field})),
dependencies_seen: MapSet.put(state.dependencies_seen, seen_dep),
unsent_dependencies: unsent_dependencies
}
else
%{
state
| dependencies_waiting_on_request:
MapSet.put(state.dependencies_waiting_on_request, {request_path, dep})
}
end
# we want to send things from non async requests
# after we've sent all info to async requests
unsent_dependencies =
if depended_on_request.async? do
state.unsent_dependencies ++ [{request_path, dep}]
else
[{request_path, dep} | state.unsent_dependencies]
end
%{
state
| dependencies:
state.dependencies
|> Map.put_new_lazy(request_path, fn -> MapSet.new() end)
|> Map.update!(request_path, &MapSet.put(&1, {dep_path, dep_field})),
reverse_dependencies:
state.reverse_dependencies
|> Map.put_new_lazy(dep_path, fn -> MapSet.new() end)
|> Map.update!(dep_path, &MapSet.put(&1, {request_path, dep_field})),
dependencies_seen: MapSet.put(state.dependencies_seen, seen_dep),
unsent_dependencies: unsent_dependencies
}
end
)
end
@ -830,6 +827,12 @@ defmodule Ash.Engine do
{async, non_async} =
requests
|> Enum.map(fn request ->
if Enum.find(state.requests, &(&1.path == request.path)) do
raise """
Attempted to add request #{inspect(request.path)} but it has already been added!
"""
end
authorize? = request.authorize? and state.authorize?
%{
@ -847,6 +850,26 @@ defmodule Ash.Engine do
|> Enum.split_with(& &1.async?)
%{state | requests: async ++ state.requests ++ non_async}
|> add_dependencies_waiting_on_request()
end
defp add_dependencies_waiting_on_request(state) do
state.dependencies_waiting_on_request
|> Enum.reduce(state, fn
{request_path, dep}, state ->
dep_path = :lists.droplast(dep)
if Enum.any?(state.requests, &(&1.path == dep_path)) do
%{
state
| unsent_dependencies: [{request_path, dep} | state.unsent_dependencies],
dependencies_waiting_on_request:
MapSet.delete(state.dependencies_waiting_on_request, {request_path, dep})
}
else
state
end
end)
end
defp build_dependencies(request, dependencies) do

View file

@ -1018,6 +1018,15 @@ defmodule Ash.Engine.Request do
end
end
result =
case result do
{:requests, requests} ->
{:requests, requests, []}
other ->
other
end
case result do
{:new_deps, new_deps} ->
log(request, fn -> "New dependencies for #{field}: #{inspect(new_deps)}" end)
@ -1031,7 +1040,7 @@ defmodule Ash.Engine.Request do
{:skipped, new_request, notifications, new_deps}
{:requests, requests} ->
{:requests, requests, new_deps} ->
log(request, fn ->
paths =
Enum.map(requests, fn
@ -1046,13 +1055,15 @@ defmodule Ash.Engine.Request do
end)
new_deps =
Enum.flat_map(requests, fn
requests
|> Enum.flat_map(fn
{request, key} ->
[request.path ++ [key]]
_request ->
[]
end)
|> Enum.concat(new_deps)
new_unresolved =
Map.update!(

View file

@ -37,6 +37,10 @@ defmodule Ash.Expr do
soft_escape(%Ash.Query.Ref{relationship_path: [], attribute: op}, escape?)
end
def do_expr({:__aliases__, _, _} = expr, _escape?) do
expr
end
def do_expr({:^, _, [value]}, _escape?) do
value
end

View file

@ -115,7 +115,18 @@ defmodule Ash.Flow.Executor.AshEngine do
end)
end
@deps_keys [:input, :over, :record, :wait_for, :halt_if, :tenant, :condition]
@deps_keys [
:input,
:over,
:record,
:wait_for,
:halt_if,
:tenant,
:condition,
:resource,
:api,
:action
]
defp handle_input_templates(run_flow_steps) do
run_flow_steps
@ -511,14 +522,7 @@ defmodule Ash.Flow.Executor.AshEngine do
context: context,
transaction_name: transaction_name
)
)
|> Enum.map(fn request ->
if request.path == :lists.droplast(output_path) do
{request, :data}
else
request
end
end)}
), [output_path]}
else
{:ok, nil}
end
@ -804,60 +808,76 @@ defmodule Ash.Flow.Executor.AshEngine do
halt_reason: halt_reason
} = read
%{
action: action,
action_input: action_input,
dep_paths: dep_paths,
tenant: tenant,
request_deps: request_deps
} =
action_request_info(
all_steps,
resource,
action,
action_input,
List.wrap(
maybe_dynamic(
[resource, action],
name,
[name, :data],
input,
all_steps,
transaction_name,
tenant,
[wait_for, halt_if]
additional_context,
fn resource, action ->
%{
action: action,
action_input: action_input,
resource: resource,
api: api,
dep_paths: dep_paths,
tenant: tenant,
request_deps: request_deps
} =
action_request_info(
all_steps,
resource,
action,
api,
action_input,
input,
transaction_name,
tenant,
[wait_for, halt_if]
)
Ash.Actions.Read.as_requests([name], resource, api, action,
error_path: List.wrap(name),
authorize?: opts[:authorize?],
actor: opts[:actor],
tracer: opts[:tracer],
query_dependencies: request_deps,
get?: get? || action.get?,
tenant: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
tenant
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end,
modify_query: fn query, context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
halt_if(halt_if, halt_reason, name, results, context, fn -> query end)
end,
query_input: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
action_input
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end
)
end
)
Ash.Actions.Read.as_requests([name], resource, api, action,
error_path: List.wrap(name),
authorize?: opts[:authorize?],
actor: opts[:actor],
tracer: opts[:tracer],
query_dependencies: request_deps,
get?: get? || action.get?,
tenant: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
tenant
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end,
modify_query: fn query, context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
halt_if(halt_if, halt_reason, name, results, context, fn -> query end)
end,
query_input: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
action_input
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end
)
%Step{step: %Ash.Flow.Step.Create{} = create, input: input} ->
@ -872,53 +892,69 @@ defmodule Ash.Flow.Executor.AshEngine do
halt_if: halt_if
} = create
%{
action: action,
action_input: action_input,
dep_paths: dep_paths,
tenant: tenant,
request_deps: request_deps
} =
action_request_info(
all_steps,
resource,
action,
action_input,
List.wrap(
maybe_dynamic(
[resource, action],
name,
[name, :commit],
input,
all_steps,
transaction_name,
tenant,
[wait_for, halt_if]
additional_context,
fn resource, action ->
%{
action: action,
resource: resource,
api: api,
action_input: action_input,
dep_paths: dep_paths,
tenant: tenant,
request_deps: request_deps
} =
action_request_info(
all_steps,
resource,
action,
api,
action_input,
input,
transaction_name,
tenant,
[wait_for, halt_if]
)
Ash.Actions.Create.as_requests([name], resource, api, action,
error_path: List.wrap(name),
authorize?: opts[:authorize?],
actor: opts[:actor],
tracer: opts[:tracer],
changeset_dependencies: request_deps,
tenant: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
tenant
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end,
changeset_input: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
action_input
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end
)
end
)
Ash.Actions.Create.as_requests([name], resource, api, action,
error_path: List.wrap(name),
authorize?: opts[:authorize?],
actor: opts[:actor],
tracer: opts[:tracer],
changeset_dependencies: request_deps,
tenant: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
tenant
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end,
changeset_input: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
action_input
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end
)
%Step{step: %Ash.Flow.Step.Validate{only_keys: keys} = validate, input: input} ->
@ -936,6 +972,7 @@ defmodule Ash.Flow.Executor.AshEngine do
%{
action: action,
resource: resource,
action_input: action_input,
dep_paths: dep_paths,
tenant: tenant,
@ -945,6 +982,7 @@ defmodule Ash.Flow.Executor.AshEngine do
all_steps,
resource,
action,
nil,
action_input,
input,
transaction_name,
@ -1065,78 +1103,94 @@ defmodule Ash.Flow.Executor.AshEngine do
halt_reason: halt_reason
} = update
%{
action: action,
action_input: action_input,
dep_paths: dep_paths,
tenant: tenant,
request_deps: request_deps
} =
action_request_info(
all_steps,
resource,
action,
action_input,
input,
transaction_name,
tenant,
[wait_for, halt_if]
)
get_request =
get_request(
record,
input,
all_steps,
transaction_name,
List.wrap(
maybe_dynamic(
[resource, action],
name,
resource,
additional_context
[name, :commit],
input,
all_steps,
transaction_name,
additional_context,
fn resource, action ->
%{
action: action,
action_input: action_input,
resource: resource,
api: api,
dep_paths: dep_paths,
tenant: tenant,
request_deps: request_deps
} =
action_request_info(
all_steps,
resource,
action,
api,
action_input,
input,
transaction_name,
tenant,
[wait_for, halt_if]
)
get_request =
get_request(
record,
input,
all_steps,
transaction_name,
name,
resource,
additional_context
)
[
get_request
| Ash.Actions.Update.as_requests([name], resource, api, action,
error_path: List.wrap(name),
authorize?: opts[:authorize?],
actor: opts[:actor],
tracer: opts[:tracer],
changeset_dependencies: [[name, :fetch, :data] | request_deps],
skip_on_nil_record?: true,
modify_changeset: fn changeset, context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
halt_if(halt_if, halt_reason, name, results, context, fn -> changeset end)
end,
tenant: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
tenant
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end,
record: fn context ->
Ash.Flow.do_get_in(context, [name, :fetch, :data])
end,
changeset_input: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
action_input
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end
)
]
end
)
[
get_request
| Ash.Actions.Update.as_requests([name], resource, api, action,
error_path: List.wrap(name),
authorize?: opts[:authorize?],
actor: opts[:actor],
tracer: opts[:tracer],
changeset_dependencies: [[name, :fetch, :data] | request_deps],
skip_on_nil_record?: true,
modify_changeset: fn changeset, context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
halt_if(halt_if, halt_reason, name, results, context, fn -> changeset end)
end,
tenant: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
tenant
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end,
record: fn context ->
Ash.Flow.do_get_in(context, [name, :fetch, :data])
end,
changeset_input: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
action_input
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end
)
]
)
%Step{step: %Ash.Flow.Step.Destroy{} = destroy, input: input} ->
%{
@ -1152,78 +1206,148 @@ defmodule Ash.Flow.Executor.AshEngine do
halt_reason: halt_reason
} = destroy
%{
action: action,
action_input: action_input,
dep_paths: dep_paths,
tenant: tenant,
request_deps: request_deps
} =
action_request_info(
all_steps,
resource,
action,
action_input,
input,
transaction_name,
tenant,
[wait_for, halt_if]
)
get_request =
get_request(
record,
input,
all_steps,
transaction_name,
List.wrap(
maybe_dynamic(
[resource, action],
name,
resource,
additional_context
[name, :commit],
input,
all_steps,
transaction_name,
additional_context,
fn resource, action ->
%{
action: action,
action_input: action_input,
resource: resource,
api: api,
dep_paths: dep_paths,
tenant: tenant,
request_deps: request_deps
} =
action_request_info(
all_steps,
resource,
action,
api,
action_input,
input,
transaction_name,
tenant,
[wait_for, halt_if]
)
get_request =
get_request(
record,
input,
all_steps,
transaction_name,
name,
resource,
additional_context
)
[
get_request
| Ash.Actions.Destroy.as_requests([name], resource, api, action,
error_path: List.wrap(name),
authorize?: opts[:authorize?],
actor: opts[:actor],
tracer: opts[:tracer],
changeset_dependencies: [[name, :fetch, :data] | request_deps],
skip_on_nil_record?: true,
record: fn context ->
Ash.Flow.do_get_in(context, [name, :fetch, :data])
end,
modify_changeset: fn changeset, context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
halt_if(halt_if, halt_reason, name, results, context, fn -> changeset end)
end,
tenant: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
tenant
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end,
changeset_input: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
action_input
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end
)
]
end
)
)
end
end
[
get_request
| Ash.Actions.Destroy.as_requests([name], resource, api, action,
error_path: List.wrap(name),
authorize?: opts[:authorize?],
actor: opts[:actor],
tracer: opts[:tracer],
changeset_dependencies: [[name, :fetch, :data] | request_deps],
skip_on_nil_record?: true,
record: fn context ->
Ash.Flow.do_get_in(context, [name, :fetch, :data])
end,
modify_changeset: fn changeset, context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
defp maybe_dynamic(
items,
name,
request_path,
input,
all_steps,
transaction_name,
additional_context,
fun
) do
# TODO: this only really works for resource/action right now
if Enum.all?(items, &is_atom/1) do
apply(fun, items)
else
{items, deps} =
Enum.reduce(items, {[], []}, fn item, {items, deps} ->
{item, new_deps} = Ash.Flow.handle_input_template(item, input)
{[item | items], deps ++ new_deps}
end)
halt_if(halt_if, halt_reason, name, results, context, fn -> changeset end)
end,
tenant: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
dep_paths = get_dep_paths(all_steps, deps, transaction_name, [])
request_deps = dependable_request_paths(dep_paths)
tenant
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end,
changeset_input: fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
Ash.Engine.Request.new(
authorize?: false,
async?: false,
name: "Run dynamic step #{inspect(name)}",
path: [:dynamic, request_path],
error_path: [],
data:
Ash.Engine.Request.resolve(request_deps, fn context ->
context = Ash.Helpers.deep_merge_maps(context, additional_context)
results = results(dep_paths, context)
results = results(dep_paths, context)
action_input
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
end
)
]
items =
items
|> Ash.Flow.set_dependent_values(%{
results: results,
elements: Map.get(context, :_ash_engine_elements)
})
|> Ash.Flow.handle_modifiers()
requests =
fun
|> apply(Enum.reverse(items))
|> List.wrap()
{:ok, nil, %{requests: requests}}
end)
)
end
end
@ -1486,25 +1610,37 @@ defmodule Ash.Flow.Executor.AshEngine do
all_steps,
resource,
action,
api,
action_input,
input,
transaction_name,
tenant,
additional
) do
{action_input, deps} = Ash.Flow.handle_input_template(action_input, input)
{tenant, tenant_deps} = Ash.Flow.handle_input_template(tenant, input)
{_, additional_deps} = Ash.Flow.handle_input_template(additional, input)
{resource, resource_deps} = Ash.Flow.handle_input_template(resource, input)
{api, api_deps} = Ash.Flow.handle_input_template(api, input)
{action, action_deps} = Ash.Flow.handle_input_template(action, input)
action =
Ash.Resource.Info.action(resource, action) ||
raise "No such action #{action} for #{resource}"
{action_input, deps} = Ash.Flow.handle_input_template(action_input, input)
{tenant, tenant_deps} = Ash.Flow.handle_input_template(tenant, input)
{_, additional_deps} = Ash.Flow.handle_input_template(additional, input)
dep_paths = get_dep_paths(all_steps, deps ++ tenant_deps, transaction_name, additional_deps)
dep_paths =
get_dep_paths(
all_steps,
deps ++ tenant_deps ++ api_deps ++ resource_deps ++ action_deps,
transaction_name,
additional_deps
)
request_deps = dependable_request_paths(dep_paths)
%{
resource: resource,
api: api,
action: action,
action_input: action_input,
dep_paths: dep_paths,

View file

@ -2,7 +2,7 @@ defmodule Ash.Flow do
@moduledoc """
A flow is a static definition of a set of steps to be .
Seeuthe {{link:ash:guide:Flows}} guide for more.
See the {{link:ash:guide:Flows}} guide for more.
"""
@type t :: module
@ -50,7 +50,6 @@ defmodule Ash.Flow do
input: input,
result: result,
notifications: metadata[:notifications] || [],
runner_metadata: metadata[:runner_metadata],
valid?: true,
complete?: true
}
@ -66,14 +65,16 @@ defmodule Ash.Flow do
}
{:error, metadata, error} ->
complete? = complete?(error)
%Ash.Flow.Result{
flow: flow,
params: params,
input: input,
notifications: metadata[:notifications] || [],
runner_metadata: metadata[:runner_metadata],
runner_metadata: if(not complete?, do: metadata[:runner_metadata]),
valid?: false,
complete?: false,
complete?: complete?,
errors: List.wrap(error)
}
@ -83,7 +84,7 @@ defmodule Ash.Flow do
params: params,
input: input,
valid?: false,
complete?: false,
complete?: complete?(error),
errors: List.wrap(error)
}
end
@ -94,7 +95,7 @@ defmodule Ash.Flow do
params: input,
input: new_input,
valid?: false,
complete?: false,
complete?: complete?(error),
errors: List.wrap(error)
}
@ -104,7 +105,7 @@ defmodule Ash.Flow do
params: input,
input: input,
valid?: false,
complete?: false,
complete?: complete?(error),
errors: List.wrap(error)
}
end
@ -112,6 +113,14 @@ defmodule Ash.Flow do
end
end
defp complete?(%Ash.Error.Flow.Halted{}) do
false
end
defp complete?(_) do
true
end
defp add_actor(opts) do
if Keyword.has_key?(opts, :actor) do
opts

View file

@ -75,19 +75,19 @@ defmodule Ash.Flow.Step do
def shared_action_opts do
[
resource: [
type: :atom,
type: :any,
required: true,
doc: "The resource to call the action on.",
links: []
],
action: [
type: :atom,
type: :any,
required: true,
doc: "The action to call on the resource.",
links: []
],
api: [
type: :atom,
type: :any,
doc:
"The api to use when calling the action. Defaults to the api set in the `flow` section.",
links: []