# `Lightning.Invocation`
[🔗](https://github.com/OpenFn/lightning/blob/main/lib/lightning/invocation.ex#L1)

The Invocation context.

# `assemble_logs_for_job_and_run`

```elixir
@spec assemble_logs_for_job_and_run(Ecto.UUID.t(), Ecto.UUID.t()) :: binary()
```

# `assemble_logs_for_step`

```elixir
@spec assemble_logs_for_step(Lightning.Invocation.Step.t()) :: binary()
```

Return all logs for a step as a string of text, separated by new line 
 breaks

# `change_dataclip`

Returns an `%Ecto.Changeset{}` for tracking dataclip changes.

## Examples

    iex> change_dataclip(dataclip)
    %Ecto.Changeset{data: %Dataclip{}}

# `change_step`

Returns an `%Ecto.Changeset{}` for tracking step changes.

## Examples

    iex> change_step(step)
    %Ecto.Changeset{data: %Step{}}

# `count_workorders`

# `create_dataclip`

```elixir
@spec create_dataclip(attrs :: map()) ::
  {:ok, Lightning.Invocation.Dataclip.t()}
  | {:error, Ecto.Changeset.t(Lightning.Invocation.Dataclip)}
```

Creates a dataclip.

## Examples

    iex> create_dataclip(%{field: value})
    {:ok, %Dataclip{}}

    iex> create_dataclip(%{field: bad_value})
    {:error, %Ecto.Changeset{}}

# `dataclip_in_project?`

```elixir
@spec dataclip_in_project?(Ecto.UUID.t(), Ecto.UUID.t()) :: boolean()
```

Returns whether a dataclip with the given id belongs to the given project.

`false` for a malformed id or a dataclip in another project.

# `delete_dataclip`

Deletes a dataclip.

## Examples

    iex> delete_dataclip(dataclip)
    {:ok, %Dataclip{}}

    iex> delete_dataclip(dataclip)
    {:error, %Ecto.Changeset{}}

# `export_workorders`

Exports work orders by performing a series of database operations wrapped in a transaction.

This function creates an audit log, a project file record, and enqueues an export job using a transaction.
Each step is executed as part of an `Ecto.Multi` operation, ensuring atomicity.

## Parameters

  - `project` - The project for which work orders are being exported. Expected to be a map or struct with an `id` field.
  - `user` - The user initiating the export operation. Expected to be a map or struct with an `id` field.
  - `search_params` - A map of search parameters used to filter work orders to export.

## Returns

  - `{:ok, %{audit: audit, project_file: project_file, export_job: job}}` on success:
    - `audit`: The audit log entry created for the export operation.
    - `project_file`: The project file record created for the export operation.
    - `export_job`: The result of the export job enqueuing step.

  - `{:error, step, reason, changes}` if any step in the transaction fails:
    - `step`: The step where the error occurred, e.g., `:audit`, `:project_file`, or `:export_job`.
    - `reason`: The reason for the failure, typically an error atom.
    - `changes`: A map of changes up to the point of failure.

# `get_dataclip`

```elixir
@spec get_dataclip(step_or_uuid :: Lightning.Invocation.Step.t() | Ecto.UUID.t()) ::
  Lightning.Invocation.Dataclip.t() | nil
```

Gets a single dataclip given one of:

- a Dataclip uuid
- a Step model

Returns `nil` if the Dataclip does not exist.

## Examples

    iex> get_dataclip("27b73932-16c7-4a72-86a3-85d805ccff98")
    %Dataclip{}

    iex> get_dataclip("27b73932-16c7-4a72-86a3-85d805ccff98")
    nil

    iex> get_dataclip(%Step{id: "a uuid"})
    %Dataclip{}

# `get_dataclip!`

```elixir
@spec get_dataclip!(id :: Ecto.UUID.t()) :: Lightning.Invocation.Dataclip.t()
```

Gets a single dataclip.

Raises `Ecto.NoResultsError` if the Dataclip does not exist.

## Examples

    iex> get_dataclip!(123)
    %Dataclip{}

    iex> get_dataclip!(456)
    ** (Ecto.NoResultsError)

# `get_dataclip_for_run`

```elixir
@spec get_dataclip_for_run(run_id :: Ecto.UUID.t()) ::
  Lightning.Invocation.Dataclip.t() | nil
```

# `get_dataclip_query`

Query for retrieving the dataclip that was step's starting dataclip.

# `get_dataclip_with_body!`

```elixir
@spec get_dataclip_with_body!(id :: Ecto.UUID.t()) :: %{
  body_json: String.t(),
  type: atom(),
  id: Ecto.UUID.t(),
  updated_at: DateTime.t()
}
```

# `get_first_dataclip_for_run_and_job`

```elixir
@spec get_first_dataclip_for_run_and_job(
  run_id :: Ecto.UUID.t(),
  job_id :: Ecto.UUID.t()
) ::
  Lightning.Invocation.Dataclip.t() | nil
```

# `get_first_step_for_run_and_job`

```elixir
@spec get_first_step_for_run_and_job(run_id :: Ecto.UUID.t(), job_id :: Ecto.UUID.t()) ::
  Lightning.Invocation.Step.t() | nil
```

# `get_next_cron_run_dataclip`

Returns the dataclip that the scheduler will use for the next cron run
of the given trigger. Branches on cron_cursor_job_id.

# `get_output_dataclip_query`

Query for retrieving the dataclip that was the result of a successful step.

# `get_run_with_steps`

Get a run with all its steps preloaded, including work_order and workflow
for authorization checks.

# `get_step!`

```elixir
@spec get_step!(Ecto.UUID.t()) :: Lightning.Invocation.Step.t()
```

Gets a single step.

Raises `Ecto.NoResultsError` if the Step does not exist.

## Examples

    iex> get_step!(123)
    %Step{}

    iex> get_step!(456)
    ** (Ecto.NoResultsError)

# `get_step_count_for_run`

```elixir
@spec get_step_count_for_run(run_id :: Ecto.UUID.t()) :: non_neg_integer()
```

# `get_step_with_dataclips`

```elixir
@spec get_step_with_dataclips(Ecto.UUID.t(), Ecto.UUID.t() | nil) ::
  Lightning.Invocation.Step.t() | nil
```

Gets a step by ID with its input and output dataclips preloaded.
Returns nil if step not found.

Note: Dataclip body fields have `load_in_query: false` for performance,
so we use a custom preload query to explicitly select the body field.

# `get_step_with_job!`

Fetches a step and preloads the job via the step's event.

# `get_workorders_by_ids`

# `get_workorders_count_limit`

# `last_run_final_dataclip`

Returns the final dataclip from the last successful run for a trigger's
workflow. Used when cron_cursor_job_id is nil (use final run state).

Scopes by workflow (not trigger) so that manual runs are also considered.

# `last_successful_step_dataclip`

Returns the output dataclip from the last successful step for a job.
Used when cron_cursor_job_id is set to a specific job.

# `list_dataclips`

```elixir
@spec list_dataclips() :: [Lightning.Invocation.Dataclip.t()]
```

Returns the list of dataclips.

## Examples

    iex> list_dataclips()
    [%Dataclip{}, ...]

# `list_dataclips`

```elixir
@spec list_dataclips(Lightning.Projects.Project.t()) :: [
  Lightning.Invocation.Dataclip.t()
]
```

# `list_dataclips_for_job`

```elixir
@spec list_dataclips_for_job(Lightning.Workflows.Job.t(), limit :: pos_integer()) :: [
  Lightning.Invocation.Dataclip.t()
]
```

# `list_dataclips_for_job`

```elixir
@spec list_dataclips_for_job(
  Lightning.Workflows.Job.t(),
  user_filters :: map(),
  opts :: Keyword.t()
) ::
  [Lightning.Invocation.Dataclip.t()]
```

# `list_dataclips_for_job_with_cron_state`

```elixir
@spec list_dataclips_for_job_with_cron_state(
  Lightning.Workflows.Job.t(),
  user_filters :: map(),
  opts :: Keyword.t()
) :: {[Lightning.Invocation.Dataclip.t()], Ecto.UUID.t() | nil}
```

Lists dataclips for a job, including next cron run state if cron-triggered.

For cron-triggered jobs, this function will include the next run state dataclip
and return its ID even if it doesn't match the filters.

Returns a tuple of {dataclips, next_cron_run_dataclip_id}.

# `list_dataclips_query`

```elixir
@spec list_dataclips_query(Lightning.Projects.Project.t()) :: Ecto.Queryable.t()
```

# `list_steps`

Returns the list of steps.

## Examples

    iex> list_steps()
    [%Step{}, ...]

# `list_steps_for_project`

```elixir
@spec list_steps_for_project(Lightning.Projects.Project.t(), keyword() | map()) ::
  Scrivener.Page.t()
```

# `list_steps_for_project_query`

```elixir
@spec list_steps_for_project_query(Lightning.Projects.Project.t()) :: Ecto.Query.t()
```

# `logs_for_run`

```elixir
@spec logs_for_run(Ecto.UUID.t(), Ecto.UUID.t()) :: [map()]
```

Return every log line for a run in `project_id`, oldest first, as maps.

Unlike the `assemble_logs_*` functions this keeps the columns needed to tell
the lines apart: `job_id` for attribution, `level`, and `step_id` to separate
retries. Run-level lines have no step, so both ids are nil for them.

# `logs_for_step`

```elixir
@spec logs_for_step(Lightning.Invocation.Step.t()) :: list()
```

Return all logs for a step as a list

# `scrubbed_io_for_run`

```elixir
@spec scrubbed_io_for_run(Ecto.UUID.t() | String.t(), Ecto.UUID.t() | nil) :: [map()]
```

Return the input and output of every step in a run, oldest first, with the
values replaced by their types.

What comes back is the shape of the data rather than the data: every leaf
becomes `"string"`, `"number"`, `"boolean"`, `"null"` or `"unknown"`, and
long lists keep two samples. Field names survive as they are, capped in
number. See `Lightning.Scrubber.scrub_values/2`.

Every step in the run comes back, in the order it ran. A dataclip that was
never set comes back as nil; one that is erased, too large to read, or past
the point where the run stopped fitting comes back as a short sentence saying
so, because a reader that is told nothing assumes it saw everything.

# `search_workorders`

Searches for work orders based on project and search parameters.

## Parameters:
- `project`: The project to filter the work orders by.
- `search_params`: The parameters to guide the search.

## Returns:
A paginated list of work orders that match the criteria.

## Example:
    search_workorders(%Project{id: 1}, %SearchParams{status: ["completed"]})

# `search_workorders`

```elixir
@spec search_workorders(
  Lightning.Projects.Project.t(),
  Lightning.WorkOrders.SearchParams.t(),
  keyword() | map()
) :: Scrivener.Page.t(Lightning.WorkOrder.t())
```

# `search_workorders_for_cancel`

Returns work orders matching the search params that have cancellable
(available) runs. Unlike retry, does not filter on wiped dataclips since
cancellation doesn't need the dataclip.

# `search_workorders_for_export_query`

# `search_workorders_for_retry`

# `update_dataclip`

Updates a dataclip.

## Examples

    iex> update_dataclip(dataclip, %{field: new_value})
    {:ok, %Dataclip{}}

    iex> update_dataclip(dataclip, %{field: bad_value})
    {:error, %Ecto.Changeset{}}

# `update_dataclip_name`

```elixir
@spec update_dataclip_name(
  Lightning.Invocation.Dataclip.t(),
  String.t() | nil,
  Lightning.Accounts.User.t()
) :: {:ok, Lightning.Invocation.Dataclip.t()} | {:error, Ecto.Changeset.t()}
```

# `with_runs`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
