Skip to content

Commit 5043e49

Browse files
Updated GovStack API (#3786)
* update govstack api * govstack api * new test * credo * fix types * update tests and filtering response
1 parent c367bea commit 5043e49

File tree

18 files changed

+2704
-223
lines changed

18 files changed

+2704
-223
lines changed

lib/lightning/invocation/query.ex

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,168 @@ defmodule Lightning.Invocation.Query do
77
alias Lightning.Accounts.User
88
alias Lightning.Invocation.Dataclip
99
alias Lightning.Invocation.Step
10+
alias Lightning.Projects.Project
11+
alias Lightning.Run
1012
alias Lightning.Workflows.Job
13+
alias Lightning.WorkOrder
14+
15+
@doc """
16+
Work orders for a specific project, or all runs available to the requesting user
17+
"""
18+
@spec work_orders_for(User.t()) :: Ecto.Queryable.t()
19+
def work_orders_for(%User{} = user) do
20+
projects = Ecto.assoc(user, :projects) |> select([:id])
21+
22+
from(wo in WorkOrder,
23+
as: :work_order,
24+
join: w in assoc(wo, :workflow),
25+
as: :workflow,
26+
join: p in subquery(projects),
27+
on: w.project_id == p.id,
28+
order_by: [desc: wo.last_activity]
29+
)
30+
end
31+
32+
@spec work_orders_for(Project.t()) :: Ecto.Queryable.t()
33+
def work_orders_for(%Project{id: project_id}) do
34+
from(wo in WorkOrder,
35+
as: :work_order,
36+
join: w in assoc(wo, :workflow),
37+
as: :workflow,
38+
where: w.project_id == ^project_id,
39+
order_by: [desc: wo.last_activity]
40+
)
41+
end
42+
43+
@doc """
44+
Runs for a specific project, or all runs available to the requesting user
45+
"""
46+
@spec runs_for(User.t()) :: Ecto.Queryable.t()
47+
def runs_for(%User{} = user) do
48+
projects = Ecto.assoc(user, :projects) |> select([:id])
49+
50+
from(r in Run,
51+
as: :run,
52+
join: wo in assoc(r, :work_order),
53+
as: :work_order,
54+
join: w in assoc(wo, :workflow),
55+
as: :workflow,
56+
join: p in subquery(projects),
57+
on: w.project_id == p.id,
58+
order_by: [desc: r.inserted_at]
59+
)
60+
end
61+
62+
@spec runs_for(Project.t()) :: Ecto.Queryable.t()
63+
def runs_for(%Project{id: project_id}) do
64+
from(r in Run,
65+
as: :run,
66+
join: wo in assoc(r, :work_order),
67+
as: :work_order,
68+
join: w in assoc(wo, :workflow),
69+
as: :workflow,
70+
where: w.project_id == ^project_id,
71+
order_by: [desc: r.inserted_at]
72+
)
73+
end
74+
75+
@doc """
76+
Validate datetime parameters for filtering
77+
"""
78+
@spec validate_datetime_params(map(), list(String.t())) ::
79+
:ok | {:error, String.t()}
80+
def validate_datetime_params(params, keys) do
81+
keys
82+
|> Enum.find_value(fn key ->
83+
case params[key] do
84+
nil ->
85+
nil
86+
87+
value ->
88+
case parse_datetime(value) do
89+
{:ok, _} ->
90+
nil
91+
92+
:error ->
93+
{:error, "Invalid datetime format for '#{key}': #{inspect(value)}"}
94+
end
95+
end
96+
end)
97+
|> case do
98+
nil -> :ok
99+
error -> error
100+
end
101+
end
102+
103+
@doc """
104+
Filter runs by inserted_at date range
105+
"""
106+
@spec filter_runs_by_date(Ecto.Queryable.t(), map()) :: Ecto.Queryable.t()
107+
def filter_runs_by_date(query, params) do
108+
query
109+
|> filter_by_inserted_after(params["inserted_after"])
110+
|> filter_by_inserted_before(params["inserted_before"])
111+
|> filter_by_updated_after(params["updated_after"])
112+
|> filter_by_updated_before(params["updated_before"])
113+
end
114+
115+
@doc """
116+
Filter runs by various criteria
117+
"""
118+
@spec filter_runs(Ecto.Queryable.t(), map()) :: Ecto.Queryable.t()
119+
def filter_runs(query, params) do
120+
query
121+
|> filter_runs_by_date(params)
122+
|> filter_runs_by_project(params["project_id"])
123+
|> filter_runs_by_workflow(params["workflow_id"])
124+
|> filter_runs_by_work_order(params["work_order_id"])
125+
end
126+
127+
defp filter_runs_by_project(query, nil), do: query
128+
129+
defp filter_runs_by_project(query, project_id) do
130+
from([workflow: w] in query, where: w.project_id == ^project_id)
131+
end
132+
133+
defp filter_runs_by_workflow(query, nil), do: query
134+
135+
defp filter_runs_by_workflow(query, workflow_id) do
136+
from([workflow: w] in query, where: w.id == ^workflow_id)
137+
end
138+
139+
defp filter_runs_by_work_order(query, nil), do: query
140+
141+
defp filter_runs_by_work_order(query, work_order_id) do
142+
from([work_order: wo] in query, where: wo.id == ^work_order_id)
143+
end
144+
145+
defp filter_by_inserted_after(query, nil), do: query
146+
147+
defp filter_by_inserted_after(query, date_string) do
148+
{:ok, datetime} = parse_datetime(date_string)
149+
from(r in query, where: r.inserted_at >= ^datetime)
150+
end
151+
152+
defp filter_by_inserted_before(query, nil), do: query
153+
154+
defp filter_by_inserted_before(query, date_string) do
155+
{:ok, datetime} = parse_datetime(date_string)
156+
from(r in query, where: r.inserted_at <= ^datetime)
157+
end
158+
159+
defp filter_by_updated_after(query, nil), do: query
160+
161+
defp filter_by_updated_after(query, date_string) do
162+
{:ok, datetime} = parse_datetime(date_string)
163+
from(r in query, where: r.updated_at >= ^datetime)
164+
end
165+
166+
defp filter_by_updated_before(query, nil), do: query
167+
168+
defp filter_by_updated_before(query, date_string) do
169+
{:ok, datetime} = parse_datetime(date_string)
170+
from(r in query, where: r.updated_at <= ^datetime)
171+
end
11172

12173
@doc """
13174
Steps for a specific user
@@ -123,4 +284,179 @@ defmodule Lightning.Invocation.Query do
123284
]
124285
)
125286
end
287+
288+
@doc """
289+
Filter work orders by date range
290+
"""
291+
@spec filter_work_orders_by_date(Ecto.Queryable.t(), map()) ::
292+
Ecto.Queryable.t()
293+
def filter_work_orders_by_date(query, params) do
294+
query
295+
|> filter_wo_by_inserted_after(params["inserted_after"])
296+
|> filter_wo_by_inserted_before(params["inserted_before"])
297+
|> filter_wo_by_updated_after(params["updated_after"])
298+
|> filter_wo_by_updated_before(params["updated_before"])
299+
end
300+
301+
@doc """
302+
Filter work orders by various criteria
303+
"""
304+
@spec filter_work_orders(Ecto.Queryable.t(), map()) :: Ecto.Queryable.t()
305+
def filter_work_orders(query, params) do
306+
query
307+
|> filter_work_orders_by_date(params)
308+
|> filter_work_orders_by_project(params["project_id"])
309+
|> filter_work_orders_by_workflow(params["workflow_id"])
310+
end
311+
312+
defp filter_work_orders_by_project(query, nil), do: query
313+
314+
defp filter_work_orders_by_project(query, project_id) do
315+
from([workflow: w] in query, where: w.project_id == ^project_id)
316+
end
317+
318+
defp filter_work_orders_by_workflow(query, nil), do: query
319+
320+
defp filter_work_orders_by_workflow(query, workflow_id) do
321+
from([workflow: w] in query, where: w.id == ^workflow_id)
322+
end
323+
324+
defp filter_wo_by_inserted_after(query, nil), do: query
325+
326+
defp filter_wo_by_inserted_after(query, date_string) do
327+
{:ok, datetime} = parse_datetime(date_string)
328+
from(wo in query, where: wo.inserted_at >= ^datetime)
329+
end
330+
331+
defp filter_wo_by_inserted_before(query, nil), do: query
332+
333+
defp filter_wo_by_inserted_before(query, date_string) do
334+
{:ok, datetime} = parse_datetime(date_string)
335+
from(wo in query, where: wo.inserted_at <= ^datetime)
336+
end
337+
338+
defp filter_wo_by_updated_after(query, nil), do: query
339+
340+
defp filter_wo_by_updated_after(query, date_string) do
341+
{:ok, datetime} = parse_datetime(date_string)
342+
from(wo in query, where: wo.updated_at >= ^datetime)
343+
end
344+
345+
defp filter_wo_by_updated_before(query, nil), do: query
346+
347+
defp filter_wo_by_updated_before(query, date_string) do
348+
{:ok, datetime} = parse_datetime(date_string)
349+
from(wo in query, where: wo.updated_at <= ^datetime)
350+
end
351+
352+
defp parse_datetime(nil), do: :error
353+
354+
defp parse_datetime(datetime_string) when is_binary(datetime_string) do
355+
case DateTime.from_iso8601(datetime_string) do
356+
{:ok, datetime, _offset} -> {:ok, datetime}
357+
{:error, _} -> :error
358+
end
359+
end
360+
361+
defp parse_datetime(_), do: :error
362+
363+
@doc """
364+
Log lines for a specific user, filtered by their accessible projects
365+
"""
366+
@spec log_lines_for(User.t()) :: Ecto.Queryable.t()
367+
def log_lines_for(%User{} = user) do
368+
projects = Ecto.assoc(user, :projects) |> select([:id])
369+
370+
from(log in Lightning.Invocation.LogLine,
371+
as: :log,
372+
join: r in assoc(log, :run),
373+
as: :run,
374+
join: wo in assoc(r, :work_order),
375+
as: :work_order,
376+
join: w in assoc(wo, :workflow),
377+
as: :workflow,
378+
join: p in subquery(projects),
379+
on: w.project_id == p.id,
380+
order_by: [desc: log.timestamp]
381+
)
382+
end
383+
384+
@doc """
385+
Filter log lines by various criteria
386+
"""
387+
@spec filter_log_lines(Ecto.Queryable.t(), map()) :: Ecto.Queryable.t()
388+
def filter_log_lines(query, params) do
389+
query
390+
|> filter_log_by_timestamp_after(params["timestamp_after"])
391+
|> filter_log_by_timestamp_before(params["timestamp_before"])
392+
|> filter_log_by_project(params["project_id"])
393+
|> filter_log_by_workflow(params["workflow_id"])
394+
|> filter_log_by_job(params["job_id"])
395+
|> filter_log_by_work_order(params["work_order_id"])
396+
|> filter_log_by_run(params["run_id"])
397+
|> filter_log_by_level(params["level"])
398+
end
399+
400+
defp filter_log_by_timestamp_after(query, nil), do: query
401+
402+
defp filter_log_by_timestamp_after(query, date_string) do
403+
{:ok, datetime} = parse_datetime(date_string)
404+
from([log: log] in query, where: log.timestamp >= ^datetime)
405+
end
406+
407+
defp filter_log_by_timestamp_before(query, nil), do: query
408+
409+
defp filter_log_by_timestamp_before(query, date_string) do
410+
{:ok, datetime} = parse_datetime(date_string)
411+
from([log: log] in query, where: log.timestamp <= ^datetime)
412+
end
413+
414+
defp filter_log_by_project(query, nil), do: query
415+
416+
defp filter_log_by_project(query, project_id) do
417+
from([workflow: w] in query, where: w.project_id == ^project_id)
418+
end
419+
420+
defp filter_log_by_workflow(query, nil), do: query
421+
422+
defp filter_log_by_workflow(query, workflow_id) do
423+
from([work_order: wo] in query, where: wo.workflow_id == ^workflow_id)
424+
end
425+
426+
defp filter_log_by_job(query, nil), do: query
427+
428+
defp filter_log_by_job(query, job_id) do
429+
from([log: log] in query,
430+
join: s in assoc(log, :step),
431+
where: s.job_id == ^job_id
432+
)
433+
end
434+
435+
defp filter_log_by_work_order(query, nil), do: query
436+
437+
defp filter_log_by_work_order(query, work_order_id) do
438+
from([run: r] in query, where: r.work_order_id == ^work_order_id)
439+
end
440+
441+
defp filter_log_by_run(query, nil), do: query
442+
443+
defp filter_log_by_run(query, run_id) do
444+
from([log: log] in query, where: log.run_id == ^run_id)
445+
end
446+
447+
defp filter_log_by_level(query, nil), do: query
448+
449+
defp filter_log_by_level(query, level) when is_binary(level) do
450+
level_atom = String.to_existing_atom(level)
451+
452+
if level_atom in [:success, :always, :info, :warn, :error, :debug] do
453+
from([log: log] in query, where: log.level == ^level_atom)
454+
else
455+
query
456+
end
457+
rescue
458+
ArgumentError -> query
459+
end
460+
461+
defp filter_log_by_level(query, _), do: query
126462
end

lib/lightning/runs.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,24 @@ defmodule Lightning.Runs do
351351

352352
defdelegate subscribe(run), to: Events
353353

354+
@doc """
355+
Returns a query for runs belonging to a specific project
356+
"""
357+
@spec runs_for_project_query(Lightning.Projects.Project.t()) ::
358+
Ecto.Queryable.t()
359+
def runs_for_project_query(%Lightning.Projects.Project{} = project) do
360+
Lightning.Invocation.Query.runs_for(project)
361+
end
362+
363+
@doc """
364+
Returns a query for runs accessible to a user
365+
"""
366+
@spec runs_for_user_query(Lightning.Accounts.User.t()) ::
367+
Ecto.Queryable.t()
368+
def runs_for_user_query(%Lightning.Accounts.User{} = user) do
369+
Lightning.Invocation.Query.runs_for(user)
370+
end
371+
354372
@spec get_project_id_for_run(Run.t()) :: Ecto.UUID.t() | nil
355373
def get_project_id_for_run(run) do
356374
Ecto.assoc(run, [:work_order, :workflow, :project])

0 commit comments

Comments
 (0)