ora_client/
job_query.rs

1//! Query filters for jobs.
2
3use std::time::SystemTime;
4
5use ora_proto::{
6    common::v1::TimeRange,
7    server::v1::{self, JobExecutionStatus, JobQueryOrder},
8};
9use uuid::Uuid;
10
11use crate::{job_definition::JobStatus, IndexSet, JobType};
12
13/// A filter for querying jobs.
14#[derive(Debug, Clone, Default)]
15#[must_use]
16pub struct JobFilter {
17    /// The job IDs to filter by.
18    ///
19    /// If the list is empty, all jobs are included.
20    pub job_ids: IndexSet<Uuid>,
21    /// The job type IDs to filter by.
22    ///
23    /// If the list is empty, all job types are included.
24    pub job_type_ids: IndexSet<String>,
25    /// The execution IDs to filter by.
26    ///
27    /// If the list is empty, all executors are included.
28    pub execution_ids: IndexSet<Uuid>,
29    /// The schedule IDs to filter by.
30    pub schedule_ids: IndexSet<Uuid>,
31    /// A list of execution statuses to filter by.
32    /// If the list is empty, all statuses are included.
33    ///
34    /// If a job has multiple executions, the last
35    /// execution status is used.
36    ///
37    /// If a job has no executions, its status is
38    /// considered to be pending.
39    pub status: IndexSet<JobStatus>,
40    /// A list of labels to filter by.
41    ///
42    /// If multiple filters are specified, all of them
43    /// must match.
44    pub labels: Vec<JobLabelFilter>,
45    /// Only include active or inactive jobs.
46    ///
47    /// If not provided, all jobs are included.
48    pub active: Option<bool>,
49    /// Only include jobs created after the provided time.
50    ///
51    /// The time is inclusive.
52    pub created_after: Option<SystemTime>,
53    /// Only include jobs created before the provided time.
54    ///
55    /// The time is exclusive.
56    pub created_before: Option<SystemTime>,
57    /// Only include jobs with a target execution time after the provided time.
58    ///
59    /// The time is inclusive.
60    pub target_execution_time_after: Option<SystemTime>,
61    /// Only include jobs with a target execution time before the provided time.
62    ///
63    /// The time is exclusive.
64    pub target_execution_time_before: Option<SystemTime>,
65}
66
67impl JobFilter {
68    /// Create a new job filter that includes all jobs.
69    pub fn new() -> Self {
70        Self::default()
71    }
72
73    /// Filter by a specific job ID.
74    pub fn with_job_id(mut self, job_id: Uuid) -> Self {
75        self.job_ids.insert(job_id);
76        self
77    }
78
79    /// Filter by specific job IDs.
80    pub fn with_job_ids(mut self, job_ids: impl IntoIterator<Item = Uuid>) -> Self {
81        self.job_ids.extend(job_ids);
82        self
83    }
84
85    /// Filter by a specific job type ID.
86    pub fn with_job_type_id(mut self, job_type_id: impl Into<String>) -> Self {
87        self.job_type_ids.insert(job_type_id.into());
88        self
89    }
90
91    /// Filter by specific job type IDs.
92    pub fn with_job_type_ids(mut self, job_type_ids: impl IntoIterator<Item = String>) -> Self {
93        self.job_type_ids.extend(job_type_ids);
94        self
95    }
96
97    /// Filter by a specific execution ID.
98    pub fn with_execution_id(mut self, execution_id: Uuid) -> Self {
99        self.execution_ids.insert(execution_id);
100        self
101    }
102
103    /// Filter by specific execution IDs.
104    pub fn with_execution_ids(mut self, execution_ids: impl IntoIterator<Item = Uuid>) -> Self {
105        self.execution_ids.extend(execution_ids);
106        self
107    }
108
109    /// Filter by a specific schedule ID.
110    pub fn with_schedule_id(mut self, schedule_id: Uuid) -> Self {
111        self.schedule_ids.insert(schedule_id);
112        self
113    }
114
115    /// Filter by specific schedule IDs.
116    pub fn with_schedule_ids(mut self, schedule_ids: impl IntoIterator<Item = Uuid>) -> Self {
117        self.schedule_ids.extend(schedule_ids);
118        self
119    }
120
121    /// Filter by a specific status.
122    pub fn include_status(mut self, status: JobStatus) -> Self {
123        self.status.insert(status);
124        self
125    }
126
127    /// Include pending jobs.
128    pub fn include_pending(mut self) -> Self {
129        self.status.insert(JobStatus::Pending);
130        self
131    }
132
133    /// Include ready jobs.
134    pub fn include_ready(mut self) -> Self {
135        self.status.insert(JobStatus::Ready);
136        self
137    }
138
139    /// Include assigned jobs.
140    pub fn include_assigned(mut self) -> Self {
141        self.status.insert(JobStatus::Assigned);
142        self
143    }
144
145    /// Include running jobs.
146    pub fn include_running(mut self) -> Self {
147        self.status.insert(JobStatus::Running);
148        self
149    }
150
151    /// Include successful jobs.
152    pub fn include_succeeded(mut self) -> Self {
153        self.status.insert(JobStatus::Succeeded);
154        self
155    }
156
157    /// Include failed jobs.
158    pub fn include_failed(mut self) -> Self {
159        self.status.insert(JobStatus::Failed);
160        self
161    }
162
163    /// Filter by active status.
164    pub fn active_only(mut self) -> Self {
165        self.active = Some(true);
166        self
167    }
168
169    /// Filter by inactive status.
170    pub fn inactive_only(mut self) -> Self {
171        self.active = Some(false);
172        self
173    }
174
175    /// Filter by a label.
176    pub fn with_label_value(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
177        self.labels.push(JobLabelFilter {
178            key: key.into(),
179            value: JobLabelFilterValue::Equals(value.into()),
180        });
181        self
182    }
183
184    /// Filter by a label that must exist.
185    pub fn with_label(mut self, key: impl Into<String>) -> Self {
186        self.labels.push(JobLabelFilter {
187            key: key.into(),
188            value: JobLabelFilterValue::Exists,
189        });
190        self
191    }
192
193    /// Include a given job type.
194    pub fn include_job_type<J>(self) -> Self
195    where
196        J: JobType,
197    {
198        self.with_job_type_id(J::id())
199    }
200
201    /// Include jobs created after the provided time.
202    ///
203    /// The time is inclusive.
204    pub fn created_after(mut self, time: SystemTime) -> Self {
205        self.created_after = Some(time);
206        self
207    }
208
209    /// Include jobs created before the provided time.
210    ///
211    /// The time is exclusive.
212    pub fn created_before(mut self, time: SystemTime) -> Self {
213        self.created_before = Some(time);
214        self
215    }
216
217    /// Include jobs with a target execution time after the provided time.
218    ///
219    /// The time is inclusive.
220    pub fn target_execution_after(mut self, time: SystemTime) -> Self {
221        self.target_execution_time_after = Some(time);
222        self
223    }
224
225    /// Include jobs with a target execution time before the provided time.
226    ///
227    /// The time is exclusive.
228    pub fn target_execution_before(mut self, time: SystemTime) -> Self {
229        self.target_execution_time_before = Some(time);
230        self
231    }
232}
233
234impl From<JobFilter> for v1::JobQueryFilter {
235    fn from(filter: JobFilter) -> Self {
236        Self {
237            job_ids: filter
238                .job_ids
239                .into_iter()
240                .map(|id| id.to_string())
241                .collect(),
242            job_type_ids: filter
243                .job_type_ids
244                .into_iter()
245                .map(|id| id.to_string())
246                .collect(),
247            execution_ids: filter
248                .execution_ids
249                .into_iter()
250                .map(|id| id.to_string())
251                .collect(),
252            schedule_ids: filter
253                .schedule_ids
254                .into_iter()
255                .map(|id| id.to_string())
256                .collect(),
257            status: filter
258                .status
259                .into_iter()
260                .map(|s| JobExecutionStatus::from(s).into())
261                .collect(),
262            labels: filter.labels.into_iter().map(Into::into).collect(),
263            active: filter.active,
264            created_at: Some(TimeRange {
265                start: filter.created_after.map(Into::into),
266                end: filter.created_before.map(Into::into),
267            }),
268            target_execution_time: Some(TimeRange {
269                start: filter.target_execution_time_after.map(Into::into),
270                end: filter.target_execution_time_before.map(Into::into),
271            }),
272        }
273    }
274}
275
276/// A filter for querying jobs by label.
277#[derive(Debug, Clone)]
278pub struct JobLabelFilter {
279    /// The key of the label.
280    pub key: String,
281    /// The condition for the label value.
282    pub value: JobLabelFilterValue,
283}
284
285/// The condition for a label filter.
286#[derive(Debug, Clone)]
287pub enum JobLabelFilterValue {
288    /// Any label value must exist with the key.
289    Exists,
290    /// The label value must be equal to the provided value.
291    Equals(String),
292}
293
294impl From<JobLabelFilter> for v1::JobLabelFilter {
295    fn from(filter: JobLabelFilter) -> Self {
296        Self {
297            key: filter.key,
298            value: match filter.value {
299                JobLabelFilterValue::Exists => Some(v1::job_label_filter::Value::Exists(
300                    v1::LabelFilterExistCondition::Exists.into(),
301                )),
302                JobLabelFilterValue::Equals(value) => {
303                    Some(v1::job_label_filter::Value::Equals(value))
304                }
305            },
306        }
307    }
308}
309
310/// The order of jobs returned in a query.
311#[derive(Debug, Default, Clone, Copy)]
312pub enum JobOrder {
313    /// Order by the time the job was created in ascending order.
314    CreatedAtAsc,
315    /// Order by the time the job was created in descending order.
316    #[default]
317    CreatedAtDesc,
318    /// Order by the target execution time in ascending order.
319    TargetExecutionTimeAsc,
320    /// Order by the target execution time in descending order.
321    TargetExecutionTimeDesc,
322}
323
324impl From<JobOrder> for JobQueryOrder {
325    fn from(value: JobOrder) -> Self {
326        match value {
327            JobOrder::CreatedAtAsc => Self::CreatedAtAsc,
328            JobOrder::CreatedAtDesc => Self::CreatedAtDesc,
329            JobOrder::TargetExecutionTimeAsc => Self::TargetExecutionTimeAsc,
330            JobOrder::TargetExecutionTimeDesc => Self::TargetExecutionTimeDesc,
331        }
332    }
333}