@@ -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
126462end
0 commit comments