#[allow(clippy::wildcard_imports)]
use super::*;
pub fn entry_column(config: &crate::config::Config) -> Option<String> {
let inbox = config
.triage
.as_ref()
.and_then(|triage| triage.entry_status.as_deref());
let columns = &config.default_config().columns;
columns
.iter()
.find(|column| Some(column.as_str()) != inbox)
.or_else(|| columns.first())
.cloned()
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ListScope {
pub status: Option<String>,
pub me: Option<crate::assignee::AuthorIdentity>,
}
pub fn default_scope(config: &crate::config::Config, all: bool) -> ListScope {
if all {
return ListScope::default();
}
ListScope {
status: entry_column(config),
me: crate::assignee::git_identity(),
}
}
pub(super) fn is_mine(issue: &Issue, me: &crate::assignee::AuthorIdentity) -> bool {
let Some(assignee) = issue.assignee.as_deref() else {
return false;
};
if assignee == me.to_string() {
return true;
}
let handle = assignee.strip_prefix('@').unwrap_or(assignee);
if handle.eq_ignore_ascii_case(&me.name) {
return true;
}
me.email.as_deref().is_some_and(|email| {
handle.eq_ignore_ascii_case(email)
|| handle.eq_ignore_ascii_case(email.split('@').next().unwrap_or_default())
})
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ListSelection<'a> {
pub status: Option<&'a str>,
pub filter: Option<&'a str>,
pub project: Option<&'a str>,
pub epic: Option<&'a str>,
pub all: bool,
}
impl ListSelection<'_> {
pub(super) fn names_a_selection(&self) -> bool {
self.all
|| self.status.is_some()
|| self.filter.is_some()
|| self.project.is_some()
|| self.epic.is_some()
}
}
pub fn scoped_query(
store: &Store,
selection: &ListSelection<'_>,
with_archived: bool,
) -> Result<Vec<Issue>> {
let scope = default_scope(&store.config, selection.names_a_selection());
let effective = selection.status.map(str::to_string).or(scope.status);
let mut issues = query(store, effective.as_deref(), selection.filter, with_archived)?;
if let Some(me) = scope.me.as_ref() {
issues.retain(|issue| is_mine(issue, me));
}
Ok(issues)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReadyOptions {
pub limit: Option<usize>,
}
pub(super) fn is_startable(
issue: &Issue,
resolution: &crate::workflow::CategoryResolution,
board: &[String],
entry: Option<&str>,
) -> bool {
let Some(category) = crate::workflow::category_of(issue, resolution) else {
return false;
};
if !matches!(
category,
crate::workflow::WorkflowCategory::Backlog | crate::workflow::WorkflowCategory::Unstarted
) {
return false;
}
let Some(entry_index) = entry.and_then(|entry| board.iter().position(|column| column == entry))
else {
return true;
};
board
.iter()
.position(|column| column == issue.status.display())
.is_none_or(|index| index <= entry_index)
}
pub(super) fn ready_columns(issues: &[Issue]) -> Vec<ListColumn> {
let mut columns = vec![
ListColumn::Num,
ListColumn::Id,
ListColumn::Priority,
ListColumn::Title,
];
if issues.iter().any(|issue| issue.archived) {
columns.push(ListColumn::Archived);
}
columns
}
pub(super) fn sort_ready(issues: &mut [Issue]) {
issues.sort_by(|a, b| {
priority_rank(&a.priority)
.cmp(&priority_rank(&b.priority))
.then(a.created.cmp(&b.created))
.then(a.number.cmp(&b.number))
});
}
pub(super) fn priority_rank(priority: &crate::model::Priority) -> u8 {
match priority.to_string().to_ascii_lowercase().as_str() {
"critical" | "urgent" | "blocker" | "blocking" => 0,
"high" => 1,
"medium" => 2,
"low" => 3,
"" => 4,
_ => 5,
}
}
pub fn ready(
store: &Store,
columns: Option<&str>,
limit: Option<usize>,
project: Option<&str>,
epic: Option<&str>,
format: Option<&str>,
) -> Result<()> {
run(
store,
None,
None,
project,
epic,
None,
None,
columns,
None,
false,
true,
format,
Some(ReadyOptions { limit }),
)
}