Lightning.Workflows (Lightning v2.19.0-pre)
View SourceThe Workflows context.
Summary
Functions
Builds a Trigger
Creates a snapshot from a multi.
Returns an %Ecto.Changeset{} for tracking workflow changes.
Creates an edge
Whether another webhook trigger in the project already answers on this path.
Permanently deletes a workflow that has no history left.
Gets an Edge by its associated Trigger.
Returns a list of edges with jobs to execute, given a current timestamp in Unix. This is used by the scheduler, which calls this function once every minute.
Gets a single Webhook Trigger from the segments of an /i/ request path.
Gets a single workflow with optional preloads, returns nil if not found.
Gets a single workflow with optional preloads.
Gets a workflow by id, scoped to the given project.
Returns a list of workflows for a project with optional filtering, sorting, and preloading.
Whether any work orders are still recorded against workflow.
Returns the list of workflows for a project.
Returns the list of workflows.
Returns an %Ecto.Changeset{} for changing the workflow request_deletion.
Creates a latest snapshot for the given workflow if one does not already exist for the current lock_version. Returns {:ok, snapshot} if a snapshot exists or is created.
Permanently deletes workflows that were marked for deletion long enough ago and have no history left.
Computes the name_del-style name a workflow should take when it is soft
deleted, so it frees up its original name for reuse within the project.
Marks a workflow deleted and frees its name for reuse, in one step.
See Lightning.Workflows.Events.subscribe/1.
Returns a workflow name that is unique within the given project, derived
from base_name. A blank or nil base_name defaults to
"Untitled workflow". On collision, appends " 1", " 2", etc. until a free
name is found.
Updates a trigger
Updates the enabled state of triggers associated with a given workflow as a struct or as a changeset.
Checks if a workflow exists in the given project
Returns a query for workflows accessible to a user
Functions
Builds a Trigger
Creates a snapshot from a multi.
When the multi already has a :workflow change, it is assumed to be changed
or inserted and will attempt to build and insert a new snapshot.
When there isn't a :workflow change, it tries to find a dependant model
like a Job, Trigger or Edge and uses the workflow associated with that
model.
In this case we assume that the workflow wasn't actually updated,
Workflow.touch() is called to bump the updated_at and the lock_version
of the workflow before a snapshot is captured.
Returns an %Ecto.Changeset{} for tracking workflow changes.
Examples
iex> change_workflow(workflow)
%Ecto.Changeset{data: %Workflow{}}
Creates an edge
@spec custom_path_taken?(term(), Ecto.UUID.t(), term()) :: boolean()
Whether another webhook trigger in the project already answers on this path.
Advisory, so the editor can say so while the field is still open. The partial unique index is what actually guarantees it, and a save still has to handle losing the race.
@spec delete_workflow(Lightning.Workflows.Workflow.t()) :: {:ok, Lightning.Workflows.Workflow.t()} | {:error, :has_history | Ecto.Changeset.t()}
Permanently deletes a workflow that has no history left.
Returns {:error, :has_history} for a workflow that still has work orders.
Their history belongs to the project and is the data retention policy's to
remove; until it does, work orders and steps hold RESTRICT references to
the workflow's snapshots and the delete could not succeed anyway.
Deleting the workflow row cascades to its jobs, triggers, edges, snapshots, versions, templates and AI chat sessions. Project-scoped records the workflow merely referenced, dataclips above all, are left where they are: they belong to the project and outlive it.
Gets an Edge by its associated Trigger.
Parameters
%Trigger{id: trigger_id}: ATriggerstruct from which the associatedEdgeis to be found.
Returns
- Returns an
Edgestruct preloaded with itssource_triggerandtarget_jobif found. - Returns
nilif noEdgeis associated with the givenTrigger.
Examples
trigger = %Trigger{id: 1, ...}
Lightning.Workflows.get_edge_by_trigger(trigger)
# => %Edge{source_trigger: %Trigger{}, target_job: %Job{}, ...}
non_existent_trigger = %Trigger{id: 999, ...}
Lightning.Workflows.get_edge_by_trigger(non_existent_trigger)
# => nil
@spec get_edges_for_cron_execution(DateTime.t()) :: [Lightning.Workflows.Edge.t()]
Returns a list of edges with jobs to execute, given a current timestamp in Unix. This is used by the scheduler, which calls this function once every minute.
@spec get_webhook_trigger( [String.t()], keyword() ) :: Lightning.Workflows.Trigger.t() | nil
Gets a single Webhook Trigger from the segments of an /i/ request path.
Tried in order, and no step can match more than one row, so a request can never fail on an ambiguous path:
- A project id and a custom path, when there is a second segment.
- The first segment as a trigger id.
- A bare custom path, for the triggers that held one before paths were namespaced. That set is fixed at migration time and never grows.
Trailing segments are ignored: /i/<trigger-uuid>/Patient posts to the same
trigger as /i/<trigger-uuid>.
Gets a single workflow with optional preloads, returns nil if not found.
Examples
iex> get_workflow(123)
%Workflow{}
iex> get_workflow(456)
nil
iex> get_workflow(123, include: [:triggers])
%Workflow{triggers: [...]}
Gets a single workflow with optional preloads.
Raises Ecto.NoResultsError if the Workflow does not exist.
Examples
iex> get_workflow!(123)
%Workflow{}
iex> get_workflow!(456)
** (Ecto.NoResultsError)
iex> get_workflow!(123, include: [:triggers])
%Workflow{triggers: [...]}
Gets a workflow by id, scoped to the given project.
Returns nil when the id is malformed, missing, belongs to another project,
or is marked for deletion, so it can't be used to read or mutate a workflow
across projects or one on its way out.
Returns a list of workflows for a project with optional filtering, sorting, and preloading.
Parameters
project- A %Project{} struct for which to retrieve workflowsopts- Optional keyword list of options
Options
:search- String to filter workflows by name using case-insensitive partial matching:order_by- A tuple containing the field and direction to sort by, e.g.,{:name, :asc}or{:enabled, :desc}:include- List of associations to preload (defaults to [:triggers, :edges, jobs: [:workflow]])
Returns
A list of %Workflow{} structs that match the criteria
Examples
# Get all workflows for a project
iex> get_workflows_for(project)
[%Workflow{}, ...]
# Search workflows containing "api" in their name
iex> get_workflows_for(project, search: "api")
[%Workflow{name: "API Gateway"}, %Workflow{name: "External API"}]
# Sort workflows by name in descending order
iex> get_workflows_for(project, order_by: {:name, :desc})
[%Workflow{name: "Zebra"}, %Workflow{name: "Apple"}]
# Search and sort combined
iex> get_workflows_for(project, search: "api", order_by: {:name, :desc})
[%Workflow{name: "REST API"}, %Workflow{name: "API Gateway"}]
# Customize preloaded associations
iex> get_workflows_for(project, include: [:triggers])
[%Workflow{triggers: [...]}, ...]
@spec has_history?(Lightning.Workflows.Workflow.t()) :: boolean()
Whether any work orders are still recorded against workflow.
Returns the list of workflows for a project.
Examples
iex> list_project_workflows(project_id)
[%Workflow{}, ...]
Returns the list of workflows.
Examples
iex> list_workflows()
[%Workflow{}, ...]
Returns an %Ecto.Changeset{} for changing the workflow request_deletion.
Examples
iex> change_request_deletion(workflow)
%Ecto.Changeset{data: %Workflow{}}
@spec maybe_create_latest_snapshot(Lightning.Workflows.Workflow.t()) :: {:ok, Lightning.Workflows.Snapshot.t()} | {:error, Ecto.Changeset.t(Lightning.Workflows.Snapshot.t())}
Creates a latest snapshot for the given workflow if one does not already exist for the current lock_version. Returns {:ok, snapshot} if a snapshot exists or is created.
Note
In normal situations this function is not needed as the snapshot is created when the workflow is saved.
Permanently deletes workflows that were marked for deletion long enough ago and have no history left.
A workflow becomes purgeable purge_deleted_after_days days after it was
marked for deletion, and only once the last of its work orders is gone.
Deleting a workflow's history is the data retention policy's job, not this
one's: a workflow whose project keeps history forever is never purged, and
one whose retention window is shorter than the purge window is purged on the
first nightly sweep after its history expires.
An unset purge_deleted_after_days reads as zero days, in line with the
other purge workers — the cron entry driving this is only registered when
the setting is greater than zero.
@spec project_workflows_using_credentials([project_credential :: Ecto.UUID.t(), ...]) :: %{ optional(project :: Ecto.UUID.t()) => [workflow_name :: binary(), ...] }
@spec resolve_name_for_pending_deletion(Lightning.Workflows.Workflow.t()) :: String.t()
Computes the name_del-style name a workflow should take when it is soft
deleted, so it frees up its original name for reuse within the project.
Used by soft_delete_changeset/1, which every delete path routes through.
@spec save_workflow( Ecto.Changeset.t(Lightning.Workflows.Workflow.t()) | map(), struct(), keyword() ) :: {:ok, Lightning.Workflows.Workflow.t()} | {:error, Ecto.Changeset.t(Lightning.Workflows.Workflow.t()) | :workflow_deleted | :snapshot_failed}
@spec soft_delete_changeset(Ecto.Changeset.t(Lightning.Workflows.Workflow.t())) :: Ecto.Changeset.t(Lightning.Workflows.Workflow.t())
Marks a workflow deleted and frees its name for reuse, in one step.
The single soft-delete transition both mark_for_deletion/3 and the
provisioner route through, so a deleted workflow can never keep its name
reserved on a hidden row.
See Lightning.Workflows.Events.subscribe/1.
@spec to_project_space([Lightning.Workflows.Workflow.t()]) :: %{}
@spec unique_workflow_name(String.t() | nil, Ecto.UUID.t(), keyword()) :: String.t()
Returns a workflow name that is unique within the given project, derived
from base_name. A blank or nil base_name defaults to
"Untitled workflow". On collision, appends " 1", " 2", etc. until a free
name is found.
The check includes soft-deleted rows because the unique index on
[:name, :project_id] is not partial. (Delete paths rename workflows to
<name>_del via soft_delete_changeset/1, so in practice deletion frees
the original name — but any row still occupying a name must be avoided.)
Note: this is check-then-insert, so two concurrent saves can still compute
the same name and one will lose on the unique constraint. Callers already
handle that {:error, changeset}; no retry is attempted here.
Options
:exclude_workflow_id- a workflow id whose current name should not count as a clash. Pass the workflow being renamed/edited so its own name doesn't get " 1" appended on every save.
Updates a trigger
Updates the enabled state of triggers associated with a given workflow as a struct or as a changeset.
Parameters
workflow_or_changeset:- An
%Ecto.Changeset{}containing a:triggersassociation. - A
%Workflow{}struct with atriggersfield. enabled?:- A boolean indicating whether to enable (
true) or disable (false) the triggers.
Returns
- An updated
%Ecto.Changeset{}with the:triggersassociation modified. - An updated
%Ecto.Changeset{}derived from the given%Workflow{}.
Examples
Using an Ecto.Changeset
changeset = Ecto.Changeset.change(%Workflow{}, %{triggers: [%Trigger{enabled: false}]})
updated_changeset = update_triggers_enabled_state(changeset, true)
# The triggers in the changeset will now have `enabled: true`.Using a Workflow struct
workflow = %Workflow{triggers: [%Trigger{enabled: false}]}
updated_changeset = update_triggers_enabled_state(workflow, true)
# The returned changeset will have triggers with `enabled: true`.
Checks if a workflow exists in the given project
@spec workflows_for_user_query(Lightning.Accounts.User.t()) :: Ecto.Queryable.t()
Returns a query for workflows accessible to a user