Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ and this project adheres to
[#4517](https://github.com/OpenFn/lightning/pull/4517)
- Allow users to export all collection items as a JSON file.
[#4527](https://github.com/OpenFn/lightning/issues/4527)
- Ability to filter work orders and runs via REST API by UUIDs or status; added
example curl requests to REST API docs.
[#4552](https://github.com/OpenFn/lightning/issues/4552)

### Changed

Expand Down
93 changes: 93 additions & 0 deletions lib/lightning/invocation/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ defmodule Lightning.Invocation.Query do
|> filter_runs_by_project(params["project_id"])
|> filter_runs_by_workflow(params["workflow_id"])
|> filter_runs_by_work_order(params["work_order_id"])
|> filter_runs_by_state(params["state"])
end

defp filter_runs_by_project(query, nil), do: query
Expand All @@ -142,6 +143,43 @@ defmodule Lightning.Invocation.Query do
from([work_order: wo] in query, where: wo.id == ^work_order_id)
end

@valid_run_states Lightning.Run.states() |> Enum.map(&to_string/1)

defp filter_runs_by_state(query, nil), do: query

defp filter_runs_by_state(query, state) when is_binary(state) do
states =
state
|> String.split(",", trim: true)
|> Enum.map(&String.to_existing_atom/1)

from(r in query, where: r.state in ^states)
end

@doc """
Validates the `state` query parameter against known run states.

Returns `:ok` or `{:error, message}`.
"""
@spec validate_run_state_param(map()) :: :ok | {:error, String.t()}
def validate_run_state_param(%{"state" => state}) when is_binary(state) do
invalid =
state
|> String.split(",", trim: true)
|> Enum.reject(&(&1 in @valid_run_states))

case invalid do
[] ->
:ok

bad ->
{:error,
"Invalid state filter: #{inspect(bad)}. Valid states are: #{Enum.join(@valid_run_states, ", ")}"}
end
end

def validate_run_state_param(_params), do: :ok

defp filter_by_inserted_after(query, nil), do: query

defp filter_by_inserted_after(query, date_string) do
Expand Down Expand Up @@ -304,9 +342,27 @@ defmodule Lightning.Invocation.Query do
@spec filter_work_orders(Ecto.Queryable.t(), map()) :: Ecto.Queryable.t()
def filter_work_orders(query, params) do
query
|> filter_work_orders_by_ids(params["id"])
|> filter_work_orders_by_date(params)
|> filter_work_orders_by_project(params["project_id"])
|> filter_work_orders_by_workflow(params["workflow_id"])
|> filter_work_orders_by_state(params["state"])
end

defp filter_work_orders_by_ids(query, nil), do: query

defp filter_work_orders_by_ids(query, ids) when is_list(ids) do
from(wo in query, where: wo.id in ^ids)
end

defp filter_work_orders_by_ids(query, id) when is_binary(id) do
case String.split(id, ",", trim: true) do
[single_id] ->
from(wo in query, where: wo.id == ^single_id)

ids ->
from(wo in query, where: wo.id in ^ids)
end
end

defp filter_work_orders_by_project(query, nil), do: query
Expand All @@ -321,6 +377,43 @@ defmodule Lightning.Invocation.Query do
from([workflow: w] in query, where: w.id == ^workflow_id)
end

@valid_states Lightning.WorkOrder.states() |> Enum.map(&to_string/1)

defp filter_work_orders_by_state(query, nil), do: query

defp filter_work_orders_by_state(query, state) when is_binary(state) do
states =
state
|> String.split(",", trim: true)
|> Enum.map(&String.to_existing_atom/1)

from(wo in query, where: wo.state in ^states)
end

@doc """
Validates the `state` query parameter against known work order states.

Returns `:ok` or `{:error, message}`.
"""
@spec validate_state_param(map()) :: :ok | {:error, String.t()}
def validate_state_param(%{"state" => state}) when is_binary(state) do
invalid =
state
|> String.split(",", trim: true)
|> Enum.reject(&(&1 in @valid_states))

case invalid do
[] ->
:ok

bad ->
{:error,
"Invalid state filter: #{inspect(bad)}. Valid states are: #{Enum.join(@valid_states, ", ")}"}
end
end

def validate_state_param(_params), do: :ok

defp filter_wo_by_inserted_after(query, nil), do: query

defp filter_wo_by_inserted_after(query, date_string) do
Expand Down
11 changes: 9 additions & 2 deletions lib/lightning/jobs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,16 @@ defmodule Lightning.Jobs do
Gets a single job.

Returns `{:ok, job}` if found, `{:error, :not_found}` otherwise.

## Options

* `:include` - list of associations to preload (default: `[]`)

"""
def get_job(id) do
case Repo.get(Job, id) |> Repo.preload([:workflow]) do
def get_job(id, opts \\ []) do
preloads = Keyword.get(opts, :include, [])

case Repo.get(Job, id) |> Repo.preload(preloads) do
nil -> {:error, :not_found}
job -> {:ok, job}
end
Expand Down
17 changes: 8 additions & 9 deletions lib/lightning/runs/run.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ defmodule Lightning.Run do
:lost
]

@states [:available, :claimed, :started] ++ @final_states

@doc """
Returns all possible states for a run.
"""
def states, do: @states

@doc """
Returns the list of final states for a run.
"""
Expand Down Expand Up @@ -98,15 +105,7 @@ defmodule Lightning.Run do
embeds_one :options, Lightning.Runs.RunOptions

field :state, Ecto.Enum,
values:
Enum.concat(
[
:available,
:claimed,
:started
],
@final_states
),
values: @states,
default: :available

field :error_type, :string
Expand Down
5 changes: 5 additions & 0 deletions lib/lightning/workorders/workorder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ defmodule Lightning.WorkOrder do
Run.final_states()
)

@doc """
Returns all possible states for a work order.
"""
def states, do: @state_values

@derive {Jason.Encoder,
only: [
:id,
Expand Down
12 changes: 4 additions & 8 deletions lib/lightning_web/controllers/api/ai_assistant_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ defmodule LightningWeb.API.AiAssistantController do
alias Lightning.Repo
import Ecto.Query

case Jobs.get_job(job_id) do
case Jobs.get_job(job_id,
include: [workflow: [project: :project_users]]
) do
{:ok, job} ->
check_job_access(job, user)

Expand All @@ -125,13 +127,7 @@ defmodule LightningWeb.API.AiAssistantController do
end

defp check_job_access(job, user) do
alias Lightning.Repo

workflow =
job.workflow
|> Repo.preload(project: [:project_users])

check_workflow_access(workflow, user)
check_workflow_access(job.workflow, user)
end

defp check_unsaved_job_access(nil, _user) do
Expand Down
35 changes: 32 additions & 3 deletions lib/lightning_web/controllers/api/job_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,36 @@ defmodule LightningWeb.API.JobController do
GET /api/jobs
GET /api/jobs?project_id=a1b2c3d4-...&page=1&page_size=20
GET /api/jobs/a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d

## Sample curl requests

List all jobs:

```bash
curl http://localhost:4000/api/jobs \\
-H "Authorization: Bearer $TOKEN"
```

Get a single job:

```bash
curl http://localhost:4000/api/jobs/$JOB_ID \\
-H "Authorization: Bearer $TOKEN"
```

Filter by project:

```bash
curl "http://localhost:4000/api/jobs?project_id=$PROJECT_ID" \\
-H "Authorization: Bearer $TOKEN"
```

Nested route — jobs for a specific project:

```bash
curl http://localhost:4000/api/projects/$PROJECT_ID/jobs \\
-H "Authorization: Bearer $TOKEN"
```
"""
use LightningWeb, :controller

Expand Down Expand Up @@ -115,14 +145,13 @@ defmodule LightningWeb.API.JobController do
"""
@spec show(Plug.Conn.t(), map()) :: Plug.Conn.t()
def show(conn, %{"id" => id}) do
with job <- Jobs.get_job!(id),
job_with_project <- Lightning.Repo.preload(job, workflow: :project),
with {:ok, job} <- Jobs.get_job(id, include: [workflow: :project]),
:ok <-
ProjectUsers
|> Permissions.can(
:access_project,
conn.assigns.current_resource,
job_with_project.workflow.project
job.workflow.project
) do
render(conn, "show.json", job: job, conn: conn)
end
Expand Down
22 changes: 21 additions & 1 deletion lib/lightning_web/controllers/api/project_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ defmodule LightningWeb.API.ProjectController do

GET /api/projects?page=1&page_size=20
GET /api/projects/a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d

## Sample curl requests

List all projects:

```bash
curl http://localhost:4000/api/projects \\
-H "Authorization: Bearer $TOKEN"
```

Get a single project:

```bash
curl http://localhost:4000/api/projects/$PROJECT_ID \\
-H "Authorization: Bearer $TOKEN"
```
"""
use LightningWeb, :controller

Expand Down Expand Up @@ -78,7 +94,8 @@ defmodule LightningWeb.API.ProjectController do
"""
@spec show(Plug.Conn.t(), map()) :: Plug.Conn.t()
def show(conn, %{"id" => id}) do
with project <- Projects.get_project(id),
with %Lightning.Projects.Project{} = project <-
Projects.get_project(id),
:ok <-
ProjectUsers
|> Permissions.can(
Expand All @@ -87,6 +104,9 @@ defmodule LightningWeb.API.ProjectController do
project
) do
render(conn, "show.json", project: project, conn: conn)
else
nil -> {:error, :not_found}
error -> error
end
end
end
Loading
Loading