Collect start date and reminder state
Already fetch the start date and reminder state for issues immediately and have them available in the struct Task.
This commit is contained in:
parent
7dc6eb305d
commit
64d28bb868
7 changed files with 148 additions and 43 deletions
136
src/collect.rs
136
src/collect.rs
|
|
@ -3,51 +3,147 @@ use crate::util;
|
|||
|
||||
use anyhow::Context as _;
|
||||
use forgejo_api::structs::Issue;
|
||||
use futures::stream::StreamExt as _;
|
||||
use futures::stream::TryStreamExt as _;
|
||||
|
||||
pub async fn collect_tasks(ctx: &crate::Context) -> anyhow::Result<Vec<task::Task>> {
|
||||
let issues = list_all_issues(ctx).await?;
|
||||
|
||||
let tasks = issues
|
||||
.into_iter()
|
||||
.map(|issue| {
|
||||
task_from_issue(&issue).with_context(|| {
|
||||
let tasks = futures::stream::iter(issues)
|
||||
.map(|issue| async move {
|
||||
task_from_issue(ctx, &issue).await.with_context(|| {
|
||||
format!(
|
||||
"Error while converting issue #{} to task",
|
||||
issue.number.unwrap_or(-1)
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()?;
|
||||
.buffer_unordered(8)
|
||||
.try_collect::<Vec<task::Task>>()
|
||||
.await?;
|
||||
|
||||
Ok(tasks)
|
||||
}
|
||||
|
||||
fn task_from_issue(issue: &Issue) -> anyhow::Result<task::Task> {
|
||||
Ok(task::Task {
|
||||
async fn task_from_issue(ctx: &crate::Context, issue: &Issue) -> anyhow::Result<task::Task> {
|
||||
let task = task::Task {
|
||||
issue_number: issue.get_number()?,
|
||||
title: issue.get_title()?,
|
||||
state: task_state_from_issue(issue)?,
|
||||
state: task_state_from_issue(ctx, issue).await?,
|
||||
recurring: task_recurring_from_issue_labels(issue)?,
|
||||
};
|
||||
log::debug!(
|
||||
"\
|
||||
Collected Task #{} — {:?}
|
||||
- State: {:?}
|
||||
- Recurring: {:?}",
|
||||
task.issue_number,
|
||||
task.title,
|
||||
task.state,
|
||||
task.recurring,
|
||||
);
|
||||
Ok(task)
|
||||
}
|
||||
|
||||
async fn task_state_from_issue(ctx: &crate::Context, issue: &Issue) -> anyhow::Result<task::State> {
|
||||
match issue.get_state()? {
|
||||
forgejo_api::structs::StateType::Open => {
|
||||
if issue.due_date.is_some() {
|
||||
Ok(task::State::Scheduled(
|
||||
issue_get_schedule_info(ctx, issue).await?,
|
||||
))
|
||||
} else {
|
||||
Ok(task::State::Open)
|
||||
}
|
||||
}
|
||||
forgejo_api::structs::StateType::Closed => Ok(task::State::Completed {
|
||||
date: util::time_to_jiff(
|
||||
issue
|
||||
.closed_at
|
||||
.context("Closed issue without a closed_at date")?,
|
||||
),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
async fn issue_get_schedule_info(
|
||||
ctx: &crate::Context,
|
||||
issue: &Issue,
|
||||
) -> anyhow::Result<task::ScheduledInfo> {
|
||||
let due_date = util::time_to_jiff(
|
||||
issue
|
||||
.due_date
|
||||
.expect("schedule info for task without due_date"),
|
||||
);
|
||||
|
||||
let issue_number = issue.number.context("issue without number")?;
|
||||
|
||||
let mut timeline = ctx
|
||||
.forgejo
|
||||
.issue_get_comments_and_timeline(
|
||||
&ctx.owner,
|
||||
&ctx.repo,
|
||||
issue_number,
|
||||
forgejo_api::structs::IssueGetCommentsAndTimelineQuery {
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.all()
|
||||
.await
|
||||
.context("Failed to fetch timeline")?;
|
||||
|
||||
// Should not be necessary, but let's be safe.
|
||||
timeline.sort_by_key(|event| event.created_at);
|
||||
|
||||
let mut start_date =
|
||||
util::time_to_jiff(issue.created_at.context("no created_at date for issue")?);
|
||||
|
||||
let mut has_reminder = None;
|
||||
let mut has_urgent_reminder = None;
|
||||
for event in timeline.iter().rev() {
|
||||
match (event.r#type.as_deref(), &event.body) {
|
||||
(Some("reopen"), _) => {
|
||||
// When we find the event where the issue was last reopened, we stop.
|
||||
start_date = created_at_time(event)?;
|
||||
break;
|
||||
}
|
||||
(Some("comment"), Some(body)) => {
|
||||
if body.contains(crate::reminder::SIGNATURE_URGENT) {
|
||||
has_urgent_reminder = Some(created_at_time(event)?);
|
||||
} else if body.contains(crate::reminder::SIGNATURE_BASIC) {
|
||||
has_reminder = Some(created_at_time(event)?);
|
||||
}
|
||||
}
|
||||
// Ignore all other events.
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let reminded = if let Some(date) = has_urgent_reminder {
|
||||
task::ReminderState::RemindedUrgently { date }
|
||||
} else if let Some(date) = has_reminder {
|
||||
task::ReminderState::Reminded { date }
|
||||
} else {
|
||||
task::ReminderState::NotReminded
|
||||
};
|
||||
|
||||
Ok(task::ScheduledInfo {
|
||||
due_date,
|
||||
start_date,
|
||||
reminded,
|
||||
})
|
||||
}
|
||||
|
||||
fn task_state_from_issue(issue: &Issue) -> anyhow::Result<task::State> {
|
||||
match issue.get_state()? {
|
||||
forgejo_api::structs::StateType::Open => Ok(task::State::Open {
|
||||
due: issue.due_date.map(util::time_to_jiff),
|
||||
}),
|
||||
forgejo_api::structs::StateType::Closed => Ok(task::State::Completed {
|
||||
date: util::time_to_jiff(issue
|
||||
.closed_at
|
||||
.context("Closed issue without a closed_at date")?),
|
||||
}),
|
||||
}
|
||||
fn created_at_time(ev: &forgejo_api::structs::TimelineComment) -> anyhow::Result<jiff::Zoned> {
|
||||
Ok(util::time_to_jiff(ev.created_at.with_context(|| {
|
||||
format!("Timeline event {:?} without created_at time", ev.r#type,)
|
||||
})?))
|
||||
}
|
||||
|
||||
fn task_recurring_from_issue_labels(issue: &Issue) -> anyhow::Result<Option<task::Recurring>> {
|
||||
let labels = issue.get_label_names()?;
|
||||
let Some(recurring_label) = get_recurring_label(&labels)? else {
|
||||
return Ok(None)
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let interval = match recurring_label {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue