apalis-board-web 1.0.0-rc.8

Frontend components for managing Apalis task queues.
Documentation
use apalis_core::{backend::Statistic, task::status::Status};
use leptos_struct_table::{ColumnSort, PaginatedTableDataProvider};
use std::collections::VecDeque;

use crate::{RawTask, api::ApiClient, pages::tasks::Task};

pub struct TaskProvider {
    sorting: VecDeque<(usize, ColumnSort)>,
    queue: Option<String>,
    status: Option<Status>,
}

impl TaskProvider {
    pub fn all(status: Status) -> Self {
        Self {
            sorting: VecDeque::new(),
            queue: None,
            status: Some(status),
        }
    }
    pub fn new(queue: String) -> Self {
        Self {
            sorting: VecDeque::new(),
            queue: Some(queue),
            status: None,
        }
    }

    pub fn new_with_status(queue: String, status: Status) -> Self {
        Self {
            sorting: VecDeque::new(),
            queue: Some(queue),
            status: Some(status),
        }
    }
    fn url_sort_param_for_column(&self, column: usize) -> &'static str {
        match column {
            0 => "task_id",
            1 => "attempt",
            2 => "status",
            3 => "run_at",
            4 => "ctx",
            _ => "",
        }
    }

    fn url_sort_param_for_sort_pair(&self, pair: &(usize, ColumnSort)) -> String {
        let col = self.url_sort_param_for_column(pair.0);

        let dir = match pair.1 {
            ColumnSort::Ascending => "asc",
            ColumnSort::Descending => "desc",
            ColumnSort::None => return "".to_string(),
        };

        format!("sort={col}:{dir}")
    }

    fn get_url(&self, page_index: usize) -> String {
        let mut sort = String::new();
        for pair in &self.sorting {
            sort.push_str(&self.url_sort_param_for_sort_pair(pair));
        }
        let prefix = match &self.queue {
            None => "".to_string(),
            Some(queue) => format!("/queues/{queue}"),
        };
        format!(
            "{prefix}/tasks?{sort}&page={}&page_size={}&queue={}&status={}",
            page_index + 1,
            Self::PAGE_ROW_COUNT,
            self.queue.as_deref().unwrap_or(""),
            self.status
                .as_ref()
                .map(|s| s.to_string())
                .unwrap_or("".to_string()),
        )
    }
}

impl PaginatedTableDataProvider<Task, usize> for TaskProvider {
    const PAGE_ROW_COUNT: usize = 15;

    async fn get_page(&self, page_index: usize) -> Result<Vec<Task>, String> {
        let url = self.get_url(page_index);
        let resp: Vec<RawTask> = ApiClient::get(&url).await?;
        let tasks = resp
            .into_iter()
            .map(|t| Task {
                args: t.args,
                task_id: t.parts.task_id.unwrap().to_string(),
                attempt: t.parts.attempt.current(),
                status: t.parts.status.load(),
                run_at: t.parts.run_at,
                queue: t
                    .parts
                    .ctx
                    .as_object()
                    .and_then(|obj| {
                        obj.get("queue")
                            .or_else(|| obj.get("job_type"))
                            .and_then(|v| v.as_str())
                    })
                    .unwrap_or("")
                    .to_string(),
                meta: t.parts.ctx,
            })
            .collect();
        Ok(tasks)
    }

    async fn row_count(&self) -> Option<usize> {
        let queue = self.queue.as_deref()?;
        let url = format!("/queues/{queue}/stats");
        let resp: Vec<Statistic> = ApiClient::get(&url).await.ok()?;
        let stat_title = match &self.status {
            Some(Status::Pending) => "PENDING_JOBS",
            Some(Status::Running) => "RUNNING_JOBS",
            Some(Status::Failed) => "FAILED_JOBS",
            Some(Status::Done) => "DONE_JOBS",
            Some(Status::Killed) => "KILLED_JOBS",
            None => "TOTAL_JOBS",
            _ => return None,
        };
        let total = resp.iter().find(|s| s.title == stat_title)?;
        total.value.parse().ok()
    }

    fn set_sorting(&mut self, sorting: &VecDeque<(usize, ColumnSort)>) {
        self.sorting = sorting.clone();
    }
}