Create metric selector via JS

This commit is contained in:
Joscha 2023-10-22 01:21:32 +02:00
parent 8c7399725d
commit d82804e209
9 changed files with 187 additions and 192 deletions

View file

@ -26,7 +26,7 @@ use self::{
},
pages::{
commit::get_commit_by_hash,
graph::{get_graph, get_graph_data},
graph::{get_graph, get_graph_data, get_graph_metrics},
index::get_index,
queue::{get_queue, get_queue_delete, get_queue_inner},
run::get_run_by_id,
@ -49,6 +49,7 @@ pub async fn run(server: Server) -> somehow::Result<()> {
.typed_get(get_commit_by_hash)
.typed_get(get_graph)
.typed_get(get_graph_data)
.typed_get(get_graph_metrics)
.typed_get(get_index)
.typed_get(get_queue)
.typed_get(get_queue_delete)

View file

@ -14,108 +14,39 @@ use crate::{
config::ServerConfig,
server::web::{
base::{Base, Link, Tab},
paths::{PathGraph, PathGraphData},
paths::{PathGraph, PathGraphData, PathGraphMetrics},
r#static::{GRAPH_JS, UPLOT_CSS},
},
somehow,
};
use self::util::MetricFolder;
#[derive(Template)]
#[template(
ext = "html",
source = "
{% match self %}
{% when MetricTree::File with { name, metric } %}
<label><input type=\"checkbox\" name=\"{{ metric }}\"> {{ name }}</label>
{% when MetricTree::Folder with { name, metric, children } %}
{% if children.trees.is_empty() %}
{% if let Some(metric) = metric %}
<label><input type=\"checkbox\" name=\"{{ metric }}\"> {{ name }}/</label>
{% endif %}
{% else if let Some(metric) = metric %}
<details>
<summary><input type=\"checkbox\" name=\"{{ metric }}\"> {{ name }}/</summary>
{{ children|safe }}
</details>
{% else %}
<details class=\"no-metric\">
<summary>{{ name }}/</summary>
{{ children|safe }}
</details>
{% endif %}
{% endmatch %}
"
)]
enum MetricTree {
File {
name: String,
metric: String,
},
Folder {
name: String,
metric: Option<String>,
children: MetricForest,
},
}
#[derive(Template)]
#[template(
ext = "html",
source = "
<ul>
{% for tree in trees %}
<li>{{ tree|safe }}</li>
{% endfor %}
</ul>
"
)]
struct MetricForest {
trees: Vec<MetricTree>,
}
impl MetricForest {
fn from_forest(children: HashMap<String, MetricFolder>) -> Self {
let mut children = children.into_iter().collect::<Vec<_>>();
children.sort_unstable_by(|a, b| a.0.cmp(&b.0));
let mut trees = vec![];
for (name, mut folder) in children {
if let Some(file_metric) = folder.metric {
trees.push(MetricTree::File {
name: name.clone(),
metric: file_metric,
});
}
let is_folder = !folder.children.is_empty();
let folder_metric = folder.children.remove("").and_then(|f| f.metric);
if is_folder {
trees.push(MetricTree::Folder {
name,
metric: folder_metric,
children: Self::from_forest(folder.children),
})
}
}
Self { trees }
}
}
#[derive(Template)]
#[template(path = "pages/graph.html")]
struct Page {
link_uplot_css: Link,
link_graph_js: Link,
base: Base,
metrics: MetricForest,
}
pub async fn get_graph(
_path: PathGraph,
State(config): State<&'static ServerConfig>,
) -> somehow::Result<impl IntoResponse> {
let base = Base::new(config, Tab::Graph);
Ok(Page {
link_uplot_css: base.link(UPLOT_CSS),
link_graph_js: base.link(GRAPH_JS),
base,
})
}
#[derive(Serialize)]
struct MetricsResponse {
metrics: Vec<String>,
}
pub async fn get_graph_metrics(
_path: PathGraphMetrics,
State(db): State<SqlitePool>,
) -> somehow::Result<impl IntoResponse> {
let metrics =
@ -123,18 +54,7 @@ pub async fn get_graph(
.fetch_all(&db)
.await?;
let metrics = MetricFolder::new(metrics);
assert!(metrics.metric.is_none());
let metrics = MetricForest::from_forest(metrics.children);
let base = Base::new(config, Tab::Graph);
Ok(Page {
link_uplot_css: base.link(UPLOT_CSS),
link_graph_js: base.link(GRAPH_JS),
base,
metrics,
})
Ok(Json(MetricsResponse { metrics }))
}
#[derive(Deserialize)]

View file

@ -1,29 +1,5 @@
use std::collections::{HashMap, HashSet};
#[derive(Default)]
pub struct MetricFolder {
pub metric: Option<String>,
pub children: HashMap<String, MetricFolder>,
}
impl MetricFolder {
fn insert(&mut self, metric: String) {
let mut current = self;
for segment in metric.split('/') {
current = current.children.entry(segment.to_string()).or_default();
}
current.metric = Some(metric);
}
pub fn new(metrics: Vec<String>) -> Self {
let mut tree = Self::default();
for metric in metrics {
tree.insert(metric);
}
tree
}
}
/// Sort commits topologically such that parents come before their children.
///
/// Assumes that `parent_child_pairs` contains no duplicates and is in the

View file

@ -13,6 +13,10 @@ pub struct PathIndex {}
#[typed_path("/graph/")]
pub struct PathGraph {}
#[derive(Deserialize, TypedPath)]
#[typed_path("/graph/metrics")]
pub struct PathGraphMetrics {}
#[derive(Deserialize, TypedPath)]
#[typed_path("/graph/data")]
pub struct PathGraphData {}