use std::future::Future;
use std::time::{Duration, Instant};
use onetaskgraph_plugin_api::{DocumentQuery, PageRequest, TaskQuery, TaskSource};
use serde_json::{Value, json};
#[derive(Clone, Copy, Debug)]
pub struct Bound {
pub reads: u32,
pub interval: Duration,
}
pub const LINEAR_INDEX: Bound = Bound {
reads: 21,
interval: Duration::from_secs(1),
};
pub async fn settled<F, Fut>(
bound: Bound,
what: &str,
expected: &[String],
mut read: F,
) -> Result<(), String>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<Vec<String>, String>>,
{
let mut expected = expected.to_vec();
expected.sort();
let started = Instant::now();
let mut listed = read().await?;
let mut reads = 1;
while listed != expected && reads < bound.reads {
tokio::time::sleep(bound.interval).await;
listed = read().await?;
reads += 1;
}
if listed == expected {
return Ok(());
}
Err(format!(
"{what} came back as {listed:?} rather than {expected:?}, still after {reads} reads \
over {:?}",
started.elapsed()
))
}
pub async fn task_titles(
source: &dyn TaskSource,
query: &TaskQuery,
limit: u32,
what: &str,
) -> Result<Vec<String>, String> {
let mut titles = source
.query_tasks(
query,
&PageRequest {
cursor: None,
limit,
},
)
.await
.map_err(|error| format!("{what} could not be read: {error}"))?
.items
.into_iter()
.map(|task| task.title)
.collect::<Vec<_>>();
titles.sort();
Ok(titles)
}
pub async fn walked_task_titles(
source: &dyn TaskSource,
query: &TaskQuery,
most: usize,
what: &str,
) -> Result<Vec<String>, String> {
let mut walked = Vec::new();
let mut cursor = None;
for _ in 0..most {
let step = source
.query_tasks(query, &PageRequest { cursor, limit: 1 })
.await
.map_err(|error| format!("{what} could not be read: {error}"))?;
if step.items.len() > 1 {
return Err(format!(
"{what} returned {} rows on a page of one",
step.items.len()
));
}
walked.extend(step.items.into_iter().map(|task| task.title));
cursor = step.next;
if cursor.is_none() {
return Ok(walked);
}
}
Err(format!(
"{what} had not ended after {most} pages, having reached {walked:?}"
))
}
pub const MOST_DOCUMENT_PAGES: usize = 100;
pub async fn document_titles(
source: &dyn TaskSource,
query: &DocumentQuery,
keep: &dyn Fn(&str) -> bool,
what: &str,
) -> Result<Vec<String>, String> {
let mut titles = Vec::new();
let mut cursor = None;
for _ in 0..MOST_DOCUMENT_PAGES {
let step = source
.query_documents(
query,
&PageRequest {
cursor,
limit: onetaskgraph_linear::MAX_PAGE_SIZE,
},
)
.await
.map_err(|error| format!("{what} could not be read: {error}"))?;
titles.extend(
step.items
.into_iter()
.map(|document| document.title)
.filter(|title| keep(title)),
);
cursor = step.next;
if cursor.is_none() {
titles.sort();
return Ok(titles);
}
}
Err(format!(
"{what} had not ended after {MOST_DOCUMENT_PAGES} pages"
))
}
pub async fn settled_tasks(
bound: Bound,
source: &dyn TaskSource,
query: &TaskQuery,
what: &str,
expected: &[String],
) -> Result<(), String> {
settled(bound, what, expected, || {
task_titles(source, query, 50, what)
})
.await
}
pub async fn settled_walk(
bound: Bound,
source: &dyn TaskSource,
query: &TaskQuery,
most: usize,
what: &str,
expected: &[String],
) -> Result<(), String> {
settled(bound, what, expected, || async move {
let mut walked = walked_task_titles(source, query, most, what).await?;
walked.sort();
Ok(walked)
})
.await
}
pub const LABEL_CONNECTION: &str = "issueLabels";
pub const LABEL_VARIABLE: &str = "name";
pub async fn settled_label<F, Fut>(bound: Bound, name: &str, send: F) -> Result<(), String>
where
F: Fn(&'static str, Value) -> Fut,
Fut: Future<Output = Result<Value, String>>,
{
let what = format!("the labels named {name:?} by the lookup a write resolves it through");
let (what, send) = (what.as_str(), &send);
settled(bound, what, &[name.to_owned()], || async move {
let data = send(
onetaskgraph_linear::graphql::ISSUE_LABEL,
json!({ LABEL_VARIABLE: name }),
)
.await
.map_err(|error| format!("{what} could not be read: {error}"))?;
let found = data
.get(LABEL_CONNECTION)
.and_then(|connection| connection.get("nodes"))
.and_then(Value::as_array)
.ok_or_else(|| {
format!("{what} could not be read: no {LABEL_CONNECTION}.nodes in {data}")
})?
.len();
Ok(vec![name.to_owned(); found])
})
.await
}
pub async fn settled_documents(
bound: Bound,
source: &dyn TaskSource,
query: &DocumentQuery,
keep: &dyn Fn(&str) -> bool,
what: &str,
expected: &[String],
) -> Result<(), String> {
settled(bound, what, expected, || {
document_titles(source, query, keep, what)
})
.await
}