From 71f2534303aacc96db0b6f771ce859706ece026b Mon Sep 17 00:00:00 2001
From: Azriel Hoh
Date: Sat, 16 Mar 2024 11:06:58 +1300
Subject: [PATCH 01/15] Use `FlexDiag` to render outcome diagram.
---
crate/webi_components/Cargo.toml | 2 +-
crate/webi_components/src/flow_graph.rs | 35 +++++++++++--------------
2 files changed, 17 insertions(+), 20 deletions(-)
diff --git a/crate/webi_components/Cargo.toml b/crate/webi_components/Cargo.toml
index b3ec4e36a..3f1226b55 100644
--- a/crate/webi_components/Cargo.toml
+++ b/crate/webi_components/Cargo.toml
@@ -17,7 +17,7 @@ doctest = true
test = false
[dependencies]
-dot_ix = { workspace = true, features = ["rt", "web_components"] }
+dot_ix = { workspace = true, features = ["rt", "web_components", "flex_diag"] }
leptos = { workspace = true }
leptos_meta = { workspace = true }
leptos_router = { workspace = true }
diff --git a/crate/webi_components/src/flow_graph.rs b/crate/webi_components/src/flow_graph.rs
index 5499b1761..9f909f8e4 100644
--- a/crate/webi_components/src/flow_graph.rs
+++ b/crate/webi_components/src/flow_graph.rs
@@ -1,6 +1,9 @@
-use dot_ix::{model::common::DotSrcAndStyles, web_components::DotSvg};
+use dot_ix::{
+ model::{common::DotSrcAndStyles, info_graph::InfoGraph},
+ web_components::{DotSvg, FlexDiag},
+};
use leptos::{
- component, server_fn::error::NoCustomError, view, IntoView, ServerFnError, SignalGet,
+ component, server_fn::error::NoCustomError, view, IntoView, ServerFnError, Signal, SignalGet,
Transition,
};
@@ -19,23 +22,24 @@ pub fn FlowGraph() -> impl IntoView {
Some(progress_dot_graph)
};
- let outcome_dot_resource = leptos::create_resource(
+ let outcome_info_graph_resource = leptos::create_resource(
|| (),
- move |()| async move { outcome_dot_graph().await.unwrap() },
+ move |()| async move { outcome_info_graph().await.unwrap() },
);
- let outcome_dot_graph = move || {
- let outcome_dot_graph = outcome_dot_resource
- .get()
- .expect("Expected `outcome_dot_graph` to always be generated successfully.");
+ let outcome_info_graph = move || {
+ let outcome_info_graph =
+ Signal::from(move || outcome_info_graph_resource.get().unwrap_or_default());
- Some(outcome_dot_graph)
+ view! {
+
+ }
};
view! {
"Loading graph..."
}>
-
+ {outcome_info_graph}
}
@@ -63,11 +67,7 @@ pub async fn progress_dot_graph() -> Result Result> {
- use dot_ix::{
- model::common::{graphviz_dot_theme::GraphStyle, GraphvizDotTheme},
- rt::IntoGraphvizDotSrc,
- };
+pub async fn outcome_info_graph() -> Result> {
use peace_flow_model::FlowSpecInfo;
let flow_spec_info = leptos::use_context::().ok_or_else(|| {
@@ -75,8 +75,5 @@ pub async fn outcome_dot_graph() -> Result
Date: Mon, 6 May 2024 19:00:05 +1200
Subject: [PATCH 02/15] Upgrade `dot_ix` to `0.5.0`.
---
Cargo.toml | 2 +-
crate/flow_model/src/flow_spec_info.rs | 32 +++++++-------
crate/webi_components/src/flow_graph.rs | 4 +-
crate/webi_rt/Cargo.toml | 15 +++++++
crate/webi_rt/src/lib.rs | 1 +
.../src/flow_model/flow_spec_info.rs | 42 +++++++++----------
6 files changed, 57 insertions(+), 39 deletions(-)
create mode 100644 crate/webi_rt/Cargo.toml
create mode 100644 crate/webi_rt/src/lib.rs
diff --git a/Cargo.toml b/Cargo.toml
index f9032c58e..ae67ffa2a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -151,7 +151,7 @@ console = "0.15.8"
derivative = "2.2.0"
diff-struct = "0.5.3"
downcast-rs = "1.2.0"
-dot_ix = { version = "0.4.1", default-features = false }
+dot_ix = { version = "0.5.0", default-features = false }
dyn-clone = "1.0.17"
enser = "0.1.4"
erased-serde = "0.4.3"
diff --git a/crate/flow_model/src/flow_spec_info.rs b/crate/flow_model/src/flow_spec_info.rs
index f05496c65..b49a3972d 100644
--- a/crate/flow_model/src/flow_spec_info.rs
+++ b/crate/flow_model/src/flow_spec_info.rs
@@ -1,8 +1,8 @@
use std::collections::HashSet;
use dot_ix::model::{
- common::{EdgeId, NodeHierarchy, NodeId},
- info_graph::{GraphDir, IndexMap, InfoGraph, NodeInfo},
+ common::{EdgeId, Edges, NodeHierarchy, NodeId, NodeNames},
+ info_graph::{GraphDir, InfoGraph},
};
use fn_graph::{daggy::Walker, Edge, FnId, GraphInfo};
use peace_core::FlowId;
@@ -48,13 +48,13 @@ impl FlowSpecInfo {
);
let edges = progress_node_edges(graph_info);
- let node_infos = node_infos(graph_info);
+ let node_names = node_names(graph_info);
InfoGraph::builder()
.with_direction(GraphDir::Vertical)
.with_hierarchy(hierarchy)
.with_edges(edges)
- .with_node_infos(node_infos)
+ .with_node_names(node_names)
.build()
}
@@ -82,13 +82,13 @@ impl FlowSpecInfo {
);
let edges = outcome_node_edges(graph_info);
- let node_infos = node_infos(graph_info);
+ let node_names = node_names(graph_info);
InfoGraph::builder()
.with_direction(GraphDir::Vertical)
.with_hierarchy(hierarchy)
.with_edges(edges)
- .with_node_infos(node_infos)
+ .with_node_names(node_names)
.build()
}
}
@@ -139,9 +139,9 @@ fn outcome_node_hierarchy(
}
/// Returns the list of edges between items in the graph.
-fn outcome_node_edges(graph_info: &GraphInfo) -> IndexMap {
+fn outcome_node_edges(graph_info: &GraphInfo) -> Edges {
graph_info.iter_insertion_with_indices().fold(
- IndexMap::with_capacity(graph_info.node_count()),
+ Edges::with_capacity(graph_info.node_count()),
|mut edges, (node_index, item_spec_info)| {
//
let children = graph_info.children(node_index);
@@ -183,9 +183,9 @@ fn outcome_node_edges(graph_info: &GraphInfo) -> IndexMap) -> IndexMap {
+fn progress_node_edges(graph_info: &GraphInfo) -> Edges {
graph_info.iter_insertion_with_indices().fold(
- IndexMap::with_capacity(graph_info.node_count()),
+ Edges::with_capacity(graph_info.node_count()),
|mut edges, (node_index, item_spec_info)| {
//
let children = graph_info.children(node_index);
@@ -222,18 +222,18 @@ fn progress_node_edges(graph_info: &GraphInfo) -> IndexMap) -> IndexMap {
+fn node_names(graph_info: &GraphInfo) -> NodeNames {
graph_info.iter_insertion_with_indices().fold(
- IndexMap::with_capacity(graph_info.node_count()),
- |mut node_infos, (_node_index, item_spec_info)| {
+ NodeNames::with_capacity(graph_info.node_count()),
+ |mut node_names, (_node_index, item_spec_info)| {
let item_id = item_spec_info_to_node_id(item_spec_info);
// Note: This does not have to be the ID, it can be a human readable name.
- let node_info = NodeInfo::new(item_id.to_string());
+ let node_name = item_id.to_string();
- node_infos.insert(item_id, node_info);
+ node_names.insert(item_id, node_name);
- node_infos
+ node_names
},
)
}
diff --git a/crate/webi_components/src/flow_graph.rs b/crate/webi_components/src/flow_graph.rs
index 9f909f8e4..1809031b3 100644
--- a/crate/webi_components/src/flow_graph.rs
+++ b/crate/webi_components/src/flow_graph.rs
@@ -38,7 +38,9 @@ pub fn FlowGraph() -> impl IntoView {
view! {
"Loading graph..." }>
-
+
{outcome_info_graph}
diff --git a/crate/webi_rt/Cargo.toml b/crate/webi_rt/Cargo.toml
new file mode 100644
index 000000000..68f2ff4cb
--- /dev/null
+++ b/crate/webi_rt/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "peace_webi_rt"
+description = "Web interface runtime data types for the peace automation framework."
+documentation = "https://docs.rs/peace_webi_rt/"
+version.workspace = true
+authors.workspace = true
+edition.workspace = true
+homepage.workspace = true
+repository.workspace = true
+readme.workspace = true
+categories.workspace = true
+keywords.workspace = true
+license.workspace = true
+
+[dependencies]
diff --git a/crate/webi_rt/src/lib.rs b/crate/webi_rt/src/lib.rs
new file mode 100644
index 000000000..14cf729e3
--- /dev/null
+++ b/crate/webi_rt/src/lib.rs
@@ -0,0 +1 @@
+//! Web interface runtime data types for the peace automation framework.
diff --git a/workspace_tests/src/flow_model/flow_spec_info.rs b/workspace_tests/src/flow_model/flow_spec_info.rs
index 5a25a78bd..512f8ad52 100644
--- a/workspace_tests/src/flow_model/flow_spec_info.rs
+++ b/workspace_tests/src/flow_model/flow_spec_info.rs
@@ -5,10 +5,10 @@ use peace::{
dot_ix::{
self,
model::{
- common::{EdgeId, NodeHierarchy, NodeId},
+ common::{Edges, NodeHierarchy, NodeNames},
edge_id,
- info_graph::{GraphDir, InfoGraph, NodeInfo},
- node_id, IndexMap,
+ info_graph::{GraphDir, InfoGraph},
+ node_id,
},
},
FlowSpecInfo,
@@ -34,7 +34,7 @@ fn to_progress_info_graph() -> Result<(), Box> {
node_hierarchy.insert(node_id!("e"), NodeHierarchy::new());
node_hierarchy.insert(node_id!("f"), NodeHierarchy::new());
- let mut edges = IndexMap::::new();
+ let mut edges = Edges::new();
edges.insert(edge_id!("a__b"), [node_id!("a"), node_id!("b")]);
edges.insert(edge_id!("a__c"), [node_id!("a"), node_id!("c")]);
edges.insert(edge_id!("b__e"), [node_id!("b"), node_id!("e")]);
@@ -42,18 +42,18 @@ fn to_progress_info_graph() -> Result<(), Box> {
edges.insert(edge_id!("d__e"), [node_id!("d"), node_id!("e")]);
edges.insert(edge_id!("f__e"), [node_id!("f"), node_id!("e")]);
- let mut node_infos = IndexMap::::new();
- node_infos.insert(node_id!("a"), NodeInfo::new(String::from("a")));
- node_infos.insert(node_id!("b"), NodeInfo::new(String::from("b")));
- node_infos.insert(node_id!("c"), NodeInfo::new(String::from("c")));
- node_infos.insert(node_id!("d"), NodeInfo::new(String::from("d")));
- node_infos.insert(node_id!("e"), NodeInfo::new(String::from("e")));
- node_infos.insert(node_id!("f"), NodeInfo::new(String::from("f")));
+ let mut node_names = NodeNames::new();
+ node_names.insert(node_id!("a"), String::from("a"));
+ node_names.insert(node_id!("b"), String::from("b"));
+ node_names.insert(node_id!("c"), String::from("c"));
+ node_names.insert(node_id!("d"), String::from("d"));
+ node_names.insert(node_id!("e"), String::from("e"));
+ node_names.insert(node_id!("f"), String::from("f"));
InfoGraph::builder()
.with_direction(GraphDir::Vertical)
.with_hierarchy(node_hierarchy)
- .with_node_infos(node_infos)
+ .with_node_names(node_names)
.with_edges(edges)
.build()
};
@@ -83,24 +83,24 @@ fn to_outcome_info_graph() -> Result<(), Box> {
node_hierarchy.insert(node_id!("e"), NodeHierarchy::new());
node_hierarchy.insert(node_id!("f"), NodeHierarchy::new());
- let mut edges = IndexMap::::new();
+ let mut edges = Edges::new();
edges.insert(edge_id!("a__c"), [node_id!("a"), node_id!("c")]);
edges.insert(edge_id!("b__e"), [node_id!("b"), node_id!("e")]);
edges.insert(edge_id!("d__e"), [node_id!("d"), node_id!("e")]);
edges.insert(edge_id!("f__e"), [node_id!("f"), node_id!("e")]);
- let mut node_infos = IndexMap::::new();
- node_infos.insert(node_id!("a"), NodeInfo::new(String::from("a")));
- node_infos.insert(node_id!("b"), NodeInfo::new(String::from("b")));
- node_infos.insert(node_id!("c"), NodeInfo::new(String::from("c")));
- node_infos.insert(node_id!("d"), NodeInfo::new(String::from("d")));
- node_infos.insert(node_id!("e"), NodeInfo::new(String::from("e")));
- node_infos.insert(node_id!("f"), NodeInfo::new(String::from("f")));
+ let mut node_names = NodeNames::new();
+ node_names.insert(node_id!("a"), String::from("a"));
+ node_names.insert(node_id!("b"), String::from("b"));
+ node_names.insert(node_id!("c"), String::from("c"));
+ node_names.insert(node_id!("d"), String::from("d"));
+ node_names.insert(node_id!("e"), String::from("e"));
+ node_names.insert(node_id!("f"), String::from("f"));
InfoGraph::builder()
.with_direction(GraphDir::Vertical)
.with_hierarchy(node_hierarchy)
- .with_node_infos(node_infos)
+ .with_node_names(node_names)
.with_edges(edges)
.build()
};
From 8782509997a75301fb32a1fed3e8a1aea93b2ecd Mon Sep 17 00:00:00 2001
From: Azriel Hoh
Date: Mon, 6 May 2024 19:00:20 +1200
Subject: [PATCH 03/15] Split `WebiServer` from `WebiOutput`.
---
crate/webi_output/src/lib.rs | 3 +-
crate/webi_output/src/webi_output.rs | 82 ++-------------------
crate/webi_output/src/webi_server.rs | 105 +++++++++++++++++++++++++++
3 files changed, 113 insertions(+), 77 deletions(-)
create mode 100644 crate/webi_output/src/webi_server.rs
diff --git a/crate/webi_output/src/lib.rs b/crate/webi_output/src/lib.rs
index 99dbe34cc..7921e88a9 100644
--- a/crate/webi_output/src/lib.rs
+++ b/crate/webi_output/src/lib.rs
@@ -1,7 +1,8 @@
//! Web interface output for the peace automation framework.
-pub use crate::webi_output::WebiOutput;
+pub use crate::{webi_output::WebiOutput, webi_server::WebiServer};
pub mod assets;
mod webi_output;
+mod webi_server;
diff --git a/crate/webi_output/src/webi_output.rs b/crate/webi_output/src/webi_output.rs
index d26428264..f9ca03a50 100644
--- a/crate/webi_output/src/webi_output.rs
+++ b/crate/webi_output/src/webi_output.rs
@@ -1,17 +1,12 @@
-use std::{fmt::Debug, net::SocketAddr, path::Path};
+use std::{fmt::Debug, net::SocketAddr};
-use axum::Router;
-use futures::stream::{self, StreamExt, TryStreamExt};
-use leptos::view;
-use leptos_axum::LeptosRoutes;
use peace_flow_model::FlowSpecInfo;
use peace_fmt::Presentable;
use peace_rt_model_core::{async_trait, output::OutputWrite};
use peace_value_traits::AppError;
-use peace_webi_components::Home;
use peace_webi_model::WebiError;
-use tokio::io::AsyncWriteExt;
-use tower_http::services::ServeDir;
+
+use crate::WebiServer;
cfg_if::cfg_if! {
if #[cfg(feature = "output_progress")] {
@@ -28,7 +23,7 @@ cfg_if::cfg_if! {
}
/// An `OutputWrite` implementation that writes to web elements.
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub struct WebiOutput {
/// IP address and port to listen on.
socket_addr: Option,
@@ -50,74 +45,9 @@ impl WebiOutput {
let Self {
socket_addr,
flow_spec_info,
- } = self;
-
- // Setting this to None means we'll be using cargo-leptos and its env vars
- let conf = leptos::get_configuration(None).await.unwrap();
- let leptos_options = conf.leptos_options;
- let socket_addr = socket_addr.unwrap_or(leptos_options.site_addr);
- let routes = leptos_axum::generate_route_list(move || view! { });
-
- stream::iter(crate::assets::ASSETS.iter())
- .map(Result::<_, WebiError>::Ok)
- .try_for_each(|(path_str, contents)| async move {
- let asset_path = Path::new(path_str);
- if let Some(parent_dir) = asset_path.parent() {
- tokio::fs::create_dir_all(parent_dir)
- .await
- .map_err(|error| WebiError::AssetDirCreate {
- asset_dir: parent_dir.to_path_buf(),
- error,
- })?;
- }
-
- tokio::fs::write(asset_path, contents)
- .await
- .map_err(|error| WebiError::AssetWrite {
- asset_path: asset_path.to_path_buf(),
- error,
- })?;
-
- Ok(())
- })
- .await?;
-
- let flow_spec_info = flow_spec_info.clone();
- let router = Router::new()
- // serve the pkg directory
- .nest_service(
- "/pkg",
- ServeDir::new(Path::new(leptos_options.site_pkg_dir.as_str())),
- )
- // serve the `webi` directory
- .nest_service("/webi", ServeDir::new(Path::new("webi")))
- // serve the SSR rendered homepage
- .leptos_routes_with_context(
- &leptos_options,
- routes,
- move || leptos::provide_context(flow_spec_info.clone()),
- move || view! { },
- )
- .with_state(leptos_options);
+ } = self.clone();
- let listener = tokio::net::TcpListener::bind(socket_addr)
- .await
- .unwrap_or_else(|e| panic!("Failed to listen on {socket_addr}. Error: {e}"));
- let (Ok(()) | Err(_)) = tokio::io::stderr()
- .write_all(format!("listening on http://{}\n", socket_addr).as_bytes())
- .await;
- let (Ok(()) | Err(_)) = tokio::io::stderr()
- .write_all(
- format!(
- "working dir: {}\n",
- std::env::current_dir().unwrap().display()
- )
- .as_bytes(),
- )
- .await;
- axum::serve(listener, router)
- .await
- .map_err(|error| WebiError::ServerServe { socket_addr, error })
+ WebiServer::new(socket_addr, flow_spec_info).start().await
}
}
diff --git a/crate/webi_output/src/webi_server.rs b/crate/webi_output/src/webi_server.rs
new file mode 100644
index 000000000..354842ce9
--- /dev/null
+++ b/crate/webi_output/src/webi_server.rs
@@ -0,0 +1,105 @@
+use std::{fmt::Debug, net::SocketAddr, path::Path};
+
+use axum::Router;
+use futures::stream::{self, StreamExt, TryStreamExt};
+use leptos::view;
+use leptos_axum::LeptosRoutes;
+use peace_flow_model::FlowSpecInfo;
+use peace_webi_components::Home;
+use peace_webi_model::WebiError;
+use tokio::io::AsyncWriteExt;
+use tower_http::services::ServeDir;
+
+/// An `OutputWrite` implementation that writes to web elements.
+#[derive(Clone, Debug)]
+pub struct WebiServer {
+ /// IP address and port to listen on.
+ socket_addr: Option,
+ /// Flow to display to the user.
+ flow_spec_info: FlowSpecInfo,
+}
+
+impl WebiServer {
+ pub fn new(socket_addr: Option, flow_spec_info: FlowSpecInfo) -> Self {
+ Self {
+ socket_addr,
+ flow_spec_info,
+ }
+ }
+}
+
+impl WebiServer {
+ pub async fn start(&mut self) -> Result<(), WebiError> {
+ let Self {
+ socket_addr,
+ flow_spec_info,
+ } = self;
+
+ // Setting this to None means we'll be using cargo-leptos and its env vars
+ let conf = leptos::get_configuration(None).await.unwrap();
+ let leptos_options = conf.leptos_options;
+ let socket_addr = socket_addr.unwrap_or(leptos_options.site_addr);
+ let routes = leptos_axum::generate_route_list(move || view! { });
+
+ stream::iter(crate::assets::ASSETS.into_iter())
+ .map(Result::<_, WebiError>::Ok)
+ .try_for_each(|(path_str, contents)| async move {
+ let asset_path = Path::new(path_str);
+ if let Some(parent_dir) = asset_path.parent() {
+ tokio::fs::create_dir_all(parent_dir)
+ .await
+ .map_err(|error| WebiError::AssetDirCreate {
+ asset_dir: parent_dir.to_path_buf(),
+ error,
+ })?;
+ }
+
+ tokio::fs::write(asset_path, contents)
+ .await
+ .map_err(|error| WebiError::AssetWrite {
+ asset_path: asset_path.to_path_buf(),
+ error,
+ })?;
+
+ Ok(())
+ })
+ .await?;
+
+ let flow_spec_info = flow_spec_info.clone();
+ let router = Router::new()
+ // serve the pkg directory
+ .nest_service(
+ "/pkg",
+ ServeDir::new(Path::new(leptos_options.site_pkg_dir.as_str())),
+ )
+ // serve the `webi` directory
+ .nest_service("/webi", ServeDir::new(Path::new("webi")))
+ // serve the SSR rendered homepage
+ .leptos_routes_with_context(
+ &leptos_options,
+ routes,
+ move || leptos::provide_context(flow_spec_info.clone()),
+ move || view! { },
+ )
+ .with_state(leptos_options);
+
+ let listener = tokio::net::TcpListener::bind(socket_addr)
+ .await
+ .unwrap_or_else(|e| panic!("Failed to listen on {socket_addr}. Error: {e}"));
+ let (Ok(()) | Err(_)) = tokio::io::stderr()
+ .write_all(format!("listening on http://{}\n", socket_addr).as_bytes())
+ .await;
+ let (Ok(()) | Err(_)) = tokio::io::stderr()
+ .write_all(
+ format!(
+ "working dir: {}\n",
+ std::env::current_dir().unwrap().display()
+ )
+ .as_bytes(),
+ )
+ .await;
+ axum::serve(listener, router)
+ .await
+ .map_err(|error| WebiError::ServerServe { socket_addr, error })
+ }
+}
From 43cb09fcd5cbe861cf1a463a1745cb744420b185 Mon Sep 17 00:00:00 2001
From: Azriel Hoh
Date: Thu, 9 May 2024 13:04:34 +1200
Subject: [PATCH 04/15] Hold `OwnedOrRef` and
`OwnedOrMutRef