diff --git a/stats/stats-server/src/config/read/layout.rs b/stats/stats-server/src/config/read/layout.rs index 12fcb1c6b..a37967301 100644 --- a/stats/stats-server/src/config/read/layout.rs +++ b/stats/stats-server/src/config/read/layout.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use crate::config::{json, types::LineChartCategory}; use convert_case::{Case, Casing}; use serde::{Deserialize, Serialize}; @@ -5,6 +7,7 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct Config { + pub counters_order: Vec, pub line_chart_categories: Vec, } @@ -16,8 +19,53 @@ impl From for Config { *chart_name = chart_name.from_case(Case::Snake).to_case(Case::Camel) } } + let counters_order = value + .counters_order + .into_iter() + .map(|id| id.from_case(Case::Snake).to_case(Case::Camel)) + .collect(); Self { + counters_order, line_chart_categories, } } } + +/// Arranges the items `to_sort` according to order in `layout`. +/// +/// `push_missing_items_back`: +/// - if `true`, then items not present in +/// `layout` are placed at the end of the vector in their original +/// relative order. +/// - if `false` - at the beginning with the same logic. +pub fn sorted_items_according_to_layout( + to_sort: Vec, + layout: &Vec, + get_key: F, + push_missing_items_back: bool, +) -> Vec +where + Key: Ord, + F: Fn(&Item) -> &Key, +{ + let assigned_positions: BTreeMap<_, _> = layout + .iter() + .enumerate() + .map(|(pos, key)| (key, pos)) + .collect(); + let mut to_sort_with_new_positions: Vec<_> = to_sort + .into_iter() + .map(|item| { + let mut key = assigned_positions.get(get_key(&item)).copied(); + if push_missing_items_back { + key.get_or_insert(usize::MAX); + } + (item, key) + }) + .collect(); + to_sort_with_new_positions.sort_by_key(|p| p.1); + to_sort_with_new_positions + .into_iter() + .map(|p| p.0) + .collect() +} diff --git a/stats/stats-server/src/config/types.rs b/stats/stats-server/src/config/types.rs index 7deccfd67..995c98e29 100644 --- a/stats/stats-server/src/config/types.rs +++ b/stats/stats-server/src/config/types.rs @@ -1,6 +1,6 @@ //! Common types for the configs -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use cron::Schedule; use serde::{Deserialize, Serialize}; @@ -10,6 +10,8 @@ use stats_proto::blockscout::stats::v1 as proto_v1; use crate::runtime_setup::EnabledChartEntry; +use super::layout::sorted_items_according_to_layout; + /// `None` means 'enable if present' #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] @@ -192,11 +194,18 @@ impl LineChartCategory { self, info: &BTreeMap, ) -> proto_v1::LineChartSection { - let charts: Vec<_> = self - .charts_order - .into_iter() - .flat_map(|c: String| info.get(&c).map(|e| e.build_proto_line_chart_info(c))) + let category_charts = HashSet::<_>::from_iter(self.charts_order.iter()); + let category_infos_alphabetic_order: Vec<_> = info + .iter() + .filter(|(id, _)| category_charts.contains(id)) + .map(|(id, e)| e.build_proto_line_chart_info(id.to_string())) .collect(); + let charts = sorted_items_according_to_layout( + category_infos_alphabetic_order, + &self.charts_order, + |chart_info| &chart_info.id, + true, + ); proto_v1::LineChartSection { id: self.id, title: self.title, diff --git a/stats/stats-server/src/runtime_setup.rs b/stats/stats-server/src/runtime_setup.rs index baf77d712..e257aea6a 100644 --- a/stats/stats-server/src/runtime_setup.rs +++ b/stats/stats-server/src/runtime_setup.rs @@ -90,6 +90,7 @@ pub struct UpdateGroupEntry { pub struct RuntimeSetup { pub lines_layout: Vec, + pub counters_layout: Vec, pub update_groups: BTreeMap, pub charts_info: BTreeMap, } @@ -128,6 +129,7 @@ impl RuntimeSetup { let update_groups = Self::init_update_groups(update_groups, &charts_info)?; Ok(Self { lines_layout: layout.line_chart_categories, + counters_layout: layout.counters_order, update_groups, charts_info, })