Skip to main content

kubectl_view_allocations/
sort.rs

1use crate::qty::Qty;
2use crate::{Error, QtyByQualifier, TableNode};
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum SortDirection {
6    Asc,
7    Desc,
8}
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum SortColumnName {
12    Usage,
13    Requested,
14    Limits,
15    Allocatable,
16    Free,
17    Name,
18}
19
20#[derive(Debug, Clone)]
21pub struct SortColumn {
22    pub column: SortColumnName,
23    pub direction: SortDirection,
24}
25
26#[allow(clippy::result_large_err)]
27pub fn parse_sort_spec(s: &str) -> Result<Vec<SortColumn>, Error> {
28    s.split(',')
29        .map(|token| {
30            let parts: Vec<&str> = token.split_whitespace().collect();
31            let col_name = parts.first().copied().unwrap_or("").to_lowercase();
32            let direction_str = parts.get(1).copied().unwrap_or("asc").to_lowercase();
33
34            let column = match col_name.as_str() {
35                "usage" | "utilization" => SortColumnName::Usage,
36                "requested" => SortColumnName::Requested,
37                "limits" | "limit" => SortColumnName::Limits,
38                "allocatable" => SortColumnName::Allocatable,
39                "free" => SortColumnName::Free,
40                "name" => SortColumnName::Name,
41                other => {
42                    return Err(Error::InvalidSortColumn {
43                        name: other.to_string(),
44                    });
45                }
46            };
47
48            let direction = match direction_str.as_str() {
49                "desc" => SortDirection::Desc,
50                _ => SortDirection::Asc,
51            };
52
53            Ok(SortColumn { column, direction })
54        })
55        .collect()
56}
57
58pub(crate) fn effective_sort_spec(spec: &[SortColumn], show_utilization: bool) -> Vec<SortColumn> {
59    spec.iter()
60        .filter(|col| show_utilization || col.column != SortColumnName::Usage)
61        .cloned()
62        .collect()
63}
64
65fn compare_qty(a: Option<&Qty>, b: Option<&Qty>) -> std::cmp::Ordering {
66    match (a, b) {
67        (None, None) => std::cmp::Ordering::Equal,
68        (None, Some(_)) => std::cmp::Ordering::Greater,
69        (Some(_), None) => std::cmp::Ordering::Less,
70        (Some(a), Some(b)) => a.cmp(b),
71    }
72}
73
74fn compare_nodes_by(a: &TableNode, b: &TableNode, col: &SortColumn) -> std::cmp::Ordering {
75    let ord = match col.column {
76        SortColumnName::Name => a.key.cmp(&b.key),
77        SortColumnName::Usage => compare_qty(
78            a.quantities.as_ref().and_then(|q| q.utilization.as_ref()),
79            b.quantities.as_ref().and_then(|q| q.utilization.as_ref()),
80        ),
81        SortColumnName::Requested => compare_qty(
82            a.quantities.as_ref().and_then(|q| q.requested.as_ref()),
83            b.quantities.as_ref().and_then(|q| q.requested.as_ref()),
84        ),
85        SortColumnName::Limits => compare_qty(
86            a.quantities.as_ref().and_then(|q| q.limit.as_ref()),
87            b.quantities.as_ref().and_then(|q| q.limit.as_ref()),
88        ),
89        SortColumnName::Allocatable => compare_qty(
90            a.quantities.as_ref().and_then(|q| q.allocatable.as_ref()),
91            b.quantities.as_ref().and_then(|q| q.allocatable.as_ref()),
92        ),
93        SortColumnName::Free => compare_qty(a.free.as_ref(), b.free.as_ref()),
94    };
95    match col.direction {
96        SortDirection::Asc => ord,
97        SortDirection::Desc => ord.reverse(),
98    }
99}
100
101pub(crate) fn sort_children_recursive(
102    nodes: &mut Vec<TableNode>,
103    indices: &mut [usize],
104    depth: usize,
105    resource_depth: usize,
106    sort_spec: &[SortColumn],
107) {
108    // At the resource level (cpu/memory/pods siblings), quantities are incomparable
109    // across resource kinds → always sort by name ASC.
110    // Ancestors (depth < resource_depth) have None quantities so they naturally fall
111    // through to the name ASC tiebreaker anyway.
112    let effective: &[SortColumn] = if depth == resource_depth {
113        &[]
114    } else {
115        sort_spec
116    };
117    indices.sort_by(|&a, &b| {
118        for col in effective {
119            let ord = compare_nodes_by(&nodes[a], &nodes[b], col);
120            if ord != std::cmp::Ordering::Equal {
121                return ord;
122            }
123        }
124        nodes[a].key.cmp(&nodes[b].key)
125    });
126    for &i in indices.iter() {
127        let mut ch = std::mem::take(&mut nodes[i].children);
128        sort_children_recursive(nodes, &mut ch, depth + 1, resource_depth, sort_spec);
129        nodes[i].children = ch;
130    }
131}
132
133pub(crate) fn flatten_tree(
134    nodes: &[TableNode],
135    indices: &[usize],
136) -> Vec<(Vec<String>, Option<QtyByQualifier>, Option<Qty>)> {
137    let mut out = vec![];
138    for &i in indices {
139        out.push((
140            nodes[i].path.clone(),
141            nodes[i].quantities.clone(),
142            nodes[i].free.clone(),
143        ));
144        out.extend(flatten_tree(nodes, &nodes[i].children));
145    }
146    out
147}