diff --git a/pgml-dashboard/build.rs b/pgml-dashboard/build.rs index 236a78d8b..89143fd57 100644 --- a/pgml-dashboard/build.rs +++ b/pgml-dashboard/build.rs @@ -4,10 +4,7 @@ use std::process::Command; fn main() { println!("cargo:rerun-if-changed=migrations"); - let output = Command::new("git") - .args(["rev-parse", "HEAD"]) - .output() - .unwrap(); + let output = Command::new("git").args(["rev-parse", "HEAD"]).output().unwrap(); let git_hash = String::from_utf8(output.stdout).unwrap(); println!("cargo:rustc-env=GIT_SHA={}", git_hash); @@ -28,8 +25,7 @@ fn main() { } } - let css_version = - read_to_string("static/css/.pgml-bundle").expect("failed to read .pgml-bundle"); + let css_version = read_to_string("static/css/.pgml-bundle").expect("failed to read .pgml-bundle"); let css_version = css_version.trim(); let js_version = read_to_string("static/js/.pgml-bundle").expect("failed to read .pgml-bundle"); diff --git a/pgml-dashboard/rustfmt.toml b/pgml-dashboard/rustfmt.toml new file mode 100644 index 000000000..94ac875fa --- /dev/null +++ b/pgml-dashboard/rustfmt.toml @@ -0,0 +1 @@ +max_width=120 diff --git a/pgml-dashboard/src/api/chatbot.rs b/pgml-dashboard/src/api/chatbot.rs index 0b8978844..d5f439902 100644 --- a/pgml-dashboard/src/api/chatbot.rs +++ b/pgml-dashboard/src/api/chatbot.rs @@ -46,9 +46,9 @@ impl ChatRole { match self { ChatRole::User => "user", ChatRole::Bot => match brain { - ChatbotBrain::OpenAIGPT4 - | ChatbotBrain::TekniumOpenHermes25Mistral7B - | ChatbotBrain::Starling7b => "assistant", + ChatbotBrain::OpenAIGPT4 | ChatbotBrain::TekniumOpenHermes25Mistral7B | ChatbotBrain::Starling7b => { + "assistant" + } ChatbotBrain::GrypheMythoMaxL213b => "model", }, ChatRole::System => "system", @@ -69,11 +69,7 @@ impl ChatbotBrain { !matches!(self, Self::OpenAIGPT4) } - fn get_system_message( - &self, - knowledge_base: &KnowledgeBase, - context: &str, - ) -> anyhow::Result { + fn get_system_message(&self, knowledge_base: &KnowledgeBase, context: &str) -> anyhow::Result { match self { Self::OpenAIGPT4 => { let system_prompt = std::env::var("CHATBOT_CHATGPT_SYSTEM_PROMPT")?; @@ -242,10 +238,7 @@ impl Document { .take(32) .map(char::from) .collect(); - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(); + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(); Document { id, text: text.to_string(), @@ -275,9 +268,7 @@ async fn get_openai_chatgpt_answer(messages: M) -> anyhow::Result< .json::() .await?; - let response = response["choices"] - .as_array() - .context("No data returned from OpenAI")?[0]["message"]["content"] + let response = response["choices"].as_array().context("No data returned from OpenAI")?[0]["message"]["content"] .as_str() .context("The reponse content from OpenAI was not a string")? .to_string(); @@ -449,12 +440,11 @@ async fn do_chatbot_get_history(user: &User, limit: usize) -> anyhow::Result>() - .join("\n"); + .query() + .vector_recall( + &data.question, + &pipeline, + Some( + json!({ + "instruction": "Represent the Wikipedia question for retrieving supporting documents: " + }) + .into(), + ), + ) + .limit(5) + .fetch_all() + .await? + .into_iter() + .map(|(_, context, metadata)| format!("\n\n#### Document {}: \n{}\n\n", metadata["id"], context)) + .collect::>() + .join("\n"); let history_collection = Collection::new( "ChatHistory", @@ -590,49 +587,47 @@ async fn process_message( .await?; messages.reverse(); - let (mut history, _) = - messages - .into_iter() - .fold((Vec::new(), None), |(mut new_history, role), value| { - let current_role: ChatRole = - serde_json::from_value(value["document"]["role"].to_owned()) - .expect("Error parsing chat role"); - if let Some(role) = role { - if role == current_role { - match role { - ChatRole::User => new_history.push( - serde_json::json!({ - "role": ChatRole::Bot.to_model_specific_role(&brain), - "content": "*no response due to error*" - }) - .into(), - ), - ChatRole::Bot => new_history.push( - serde_json::json!({ - "role": ChatRole::User.to_model_specific_role(&brain), - "content": "*no response due to error*" - }) - .into(), - ), - _ => panic!("Too many system messages"), - } + let (mut history, _) = messages + .into_iter() + .fold((Vec::new(), None), |(mut new_history, role), value| { + let current_role: ChatRole = + serde_json::from_value(value["document"]["role"].to_owned()).expect("Error parsing chat role"); + if let Some(role) = role { + if role == current_role { + match role { + ChatRole::User => new_history.push( + serde_json::json!({ + "role": ChatRole::Bot.to_model_specific_role(&brain), + "content": "*no response due to error*" + }) + .into(), + ), + ChatRole::Bot => new_history.push( + serde_json::json!({ + "role": ChatRole::User.to_model_specific_role(&brain), + "content": "*no response due to error*" + }) + .into(), + ), + _ => panic!("Too many system messages"), } - let new_message: pgml::types::Json = serde_json::json!({ - "role": current_role.to_model_specific_role(&brain), - "content": value["document"]["text"] - }) - .into(); - new_history.push(new_message); - } else if matches!(current_role, ChatRole::User) { - let new_message: pgml::types::Json = serde_json::json!({ - "role": current_role.to_model_specific_role(&brain), - "content": value["document"]["text"] - }) - .into(); - new_history.push(new_message); } - (new_history, Some(current_role)) - }); + let new_message: pgml::types::Json = serde_json::json!({ + "role": current_role.to_model_specific_role(&brain), + "content": value["document"]["text"] + }) + .into(); + new_history.push(new_message); + } else if matches!(current_role, ChatRole::User) { + let new_message: pgml::types::Json = serde_json::json!({ + "role": current_role.to_model_specific_role(&brain), + "content": value["document"]["text"] + }) + .into(); + new_history.push(new_message); + } + (new_history, Some(current_role)) + }); let system_message = brain.get_system_message(&knowledge_base, &context)?; history.insert(0, system_message.into()); @@ -657,8 +652,7 @@ async fn process_message( .into(), ); - let update_history = - UpdateHistory::new(history_collection, user_document, brain, knowledge_base); + let update_history = UpdateHistory::new(history_collection, user_document, brain, knowledge_base); if brain.is_open_source() { let op = OpenSourceAI::new(Some( diff --git a/pgml-dashboard/src/api/cms.rs b/pgml-dashboard/src/api/cms.rs index 1f606b9f9..756b6514f 100644 --- a/pgml-dashboard/src/api/cms.rs +++ b/pgml-dashboard/src/api/cms.rs @@ -1,4 +1,7 @@ -use std::path::{Path, PathBuf}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; use comrak::{format_html_with_plugins, parse_document, Arena, ComrakPlugins}; use lazy_static::lazy_static; @@ -17,9 +20,45 @@ use crate::{ use serde::{Deserialize, Serialize}; lazy_static! { - static ref BLOG: Collection = Collection::new("Blog", true); - static ref CAREERS: Collection = Collection::new("Careers", true); - static ref DOCS: Collection = Collection::new("Docs", false); + static ref BLOG: Collection = Collection::new( + "Blog", + true, + HashMap::from([ + ("announcing-hnsw-support-in-our-sdk", "speeding-up-vector-recall-5x-with-hnsw"), + ("backwards-compatible-or-bust-python-inside-rust-inside-postgres/", "backwards-compatible-or-bust-python-inside-rust-inside-postgres"), + ("data-is-living-and-relational/", "data-is-living-and-relational"), + ("data-is-living-and-relational/", "data-is-living-and-relational"), + ("generating-llm-embeddings-with-open-source-models-in-postgresml/", "generating-llm-embeddings-with-open-source-models-in-postgresml"), + ("introducing-postgresml-python-sdk-build-end-to-end-vector-search-applications-without-openai-and-pinecone", "introducing-postgresml-python-sdk-build-end-to-end-vector-search-applications-without-openai-and-pin"), + ("llm-based-pipelines-with-postgresml-and-dbt", "llm-based-pipelines-with-postgresml-and-dbt-data-build-tool"), + ("oxidizing-machine-learning/", "oxidizing-machine-learning"), + ("personalize-embedding-vector-search-results-with-huggingface-and-pgvector", "personalize-embedding-results-with-application-data-in-your-database"), + ("pgml-chat-a-command-line-tool-for-deploying-low-latency-knowledge-based-chatbots-part-I", "pgml-chat-a-command-line-tool-for-deploying-low-latency-knowledge-based-chatbots-part-i"), + ("postgres-full-text-search-is-awesome/", "postgres-full-text-search-is-awesome"), + ("postgresml-is-8x-faster-than-python-http-microservices/", "postgresml-is-8-40x-faster-than-python-http-microservices"), + ("postgresml-is-8x-faster-than-python-http-microservices", "postgresml-is-8-40x-faster-than-python-http-microservices"), + ("postgresml-is-moving-to-rust-for-our-2.0-release/", "postgresml-is-moving-to-rust-for-our-2.0-release"), + ("postgresml-raises-4.7m-to-launch-serverless-ai-application-databases-based-on-postgres/", "postgresml-raises-usd4.7m-to-launch-serverless-ai-application-databases-based-on-postgres"), + ("postgresml-raises-4.7m-to-launch-serverless-ai-application-databases-based-on-postgres", "postgresml-raises-usd4.7m-to-launch-serverless-ai-application-databases-based-on-postgres"), + ("scaling-postgresml-to-one-million-requests-per-second/", "scaling-postgresml-to-1-million-requests-per-second"), + ("scaling-postgresml-to-one-million-requests-per-second", "scaling-postgresml-to-1-million-requests-per-second"), + ("which-database-that-is-the-question/", "which-database-that-is-the-question"), + ]) + ); + static ref CAREERS: Collection = Collection::new("Careers", true, HashMap::from([("a", "b")])); + static ref DOCS: Collection = Collection::new( + "Docs", + false, + HashMap::from([ + ("sdks/tutorials/semantic-search-using-instructor-model", "introduction/apis/client-sdks/tutorials/semantic-search-using-instructor-model"), + ("data-storage-and-retrieval/documents", "resources/data-storage-and-retrieval/documents"), + ("guides/setup/quick_start_with_docker", "resources/developer-docs/quick-start-with-docker"), + ("guides/transformers/setup", "resources/developer-docs/quick-start-with-docker"), + ("transformers/fine_tuning/", "introduction/apis/sql-extensions/pgml.tune"), + ("guides/predictions/overview", "introduction/apis/sql-extensions/pgml.predict/"), + ("machine-learning/supervised-learning/data-pre-processing", "introduction/apis/sql-extensions/pgml.train/data-pre-processing"), + ]) + ); } #[derive(Debug, Serialize, Deserialize)] @@ -35,6 +74,7 @@ pub struct Document { impl Document { pub async fn from_path(path: &PathBuf) -> anyhow::Result { + warn!("path: {:?}", path); let contents = tokio::fs::read_to_string(&path).await?; let parts = contents.split("---").collect::>(); @@ -45,8 +85,7 @@ impl Document { if meta.len() == 0 || meta[0].as_hash().is_none() { (None, contents) } else { - let description: Option = match meta[0]["description"].is_badvalue() - { + let description: Option = match meta[0]["description"].is_badvalue() { true => None, false => Some(meta[0]["description"].as_str().unwrap().to_string()), }; @@ -77,17 +116,10 @@ impl Document { let mut plugins = ComrakPlugins::default(); let headings = crate::utils::markdown::MarkdownHeadings::new(); plugins.render.heading_adapter = Some(&headings); - plugins.render.codefence_syntax_highlighter = - Some(&crate::utils::markdown::SyntaxHighlighter {}); + plugins.render.codefence_syntax_highlighter = Some(&crate::utils::markdown::SyntaxHighlighter {}); let mut html = vec![]; - format_html_with_plugins( - root, - &crate::utils::markdown::options(), - &mut html, - &plugins, - ) - .unwrap(); + format_html_with_plugins(root, &crate::utils::markdown::options(), &mut html, &plugins).unwrap(); let html = String::from_utf8(html).unwrap(); let document = Document { @@ -115,10 +147,12 @@ struct Collection { url_root: PathBuf, /// A hierarchical list of content in this collection index: Vec, + /// A list of old paths to new paths in this collection + redirects: HashMap<&'static str, &'static str>, } impl Collection { - pub fn new(name: &str, hide_root: bool) -> Collection { + pub fn new(name: &str, hide_root: bool, redirects: HashMap<&'static str, &'static str>) -> Collection { info!("Loading collection: {name}"); let name = name.to_owned(); let slug = name.to_lowercase(); @@ -131,6 +165,7 @@ impl Collection { root_dir, asset_dir, url_root, + redirects, ..Default::default() }; collection.build_index(hide_root); @@ -151,13 +186,30 @@ impl Collection { ) -> Result { info!("get_content: {} | {path:?}", self.name); - if origin.path().ends_with("/") { + let mut redirected = false; + match self + .redirects + .get(path.as_os_str().to_str().expect("needs to be a well formed path")) + { + Some(redirect) => { + warn!("found redirect: {} <- {:?}", redirect, path); + redirected = true; // reserved for some fallback path + path = PathBuf::from(redirect); + } + None => {} + }; + + let canonical = format!( + "https://postgresml.org{}/{}", + self.url_root.to_string_lossy(), + path.to_string_lossy() + ); + if origin.path().ends_with("/") && !redirected { path = path.join("README"); } - let path = self.root_dir.join(format!("{}.md", path.to_string_lossy())); - self.render(&path, cluster).await + self.render(&path, &canonical, cluster).await } /// Create an index of the Collection based on the SUMMARY.md from Gitbook. @@ -178,9 +230,9 @@ impl Collection { { match node { Node::List(list) => { - let mut links = self.get_sub_links(list).unwrap_or_else(|_| { - panic!("Could not parse list of index links: {summary_path:?}") - }); + let mut links = self + .get_sub_links(list) + .unwrap_or_else(|_| panic!("Could not parse list of index links: {summary_path:?}")); index.append(&mut links); } _ => { @@ -228,9 +280,8 @@ impl Collection { url = url.replace("README", ""); } let url = self.url_root.join(url); - let parent = - IndexLink::new(text.value.as_str()) - .href(&url.to_string_lossy()); + let parent = IndexLink::new(text.value.as_str()) + .href(&url.to_string_lossy()); links.push(parent); } _ => error!("unhandled link child: {node:?}"), @@ -268,6 +319,7 @@ impl Collection { async fn render<'a>( &self, path: &'a PathBuf, + canonical: &str, cluster: &Cluster, ) -> Result { let user = if cluster.context.user.is_anonymous() { @@ -292,6 +344,7 @@ impl Collection { } let layout = layout + .canonical(canonical) .nav_title(&self.name) .nav_links(&index) .toc_links(&doc.toc_links) @@ -385,6 +438,15 @@ async fn get_docs( DOCS.get_content(path, cluster, origin).await } +#[get("/user_guides/", rank = 5)] +async fn get_user_guides( + path: PathBuf, + cluster: &Cluster, + origin: &Origin<'_>, +) -> Result { + DOCS.get_content(path, cluster, origin).await +} + pub fn routes() -> Vec { routes![ get_blog, @@ -393,6 +455,7 @@ pub fn routes() -> Vec { get_careers_asset, get_docs, get_docs_asset, + get_user_guides, search ] } @@ -462,9 +525,7 @@ This is the end of the markdown format_html_with_plugins(root, &options(), &mut html, &plugins).unwrap(); let html = String::from_utf8(html).unwrap(); - assert!( - !html.contains(r#"
"#) || !html.contains(r#"
"#) - ); + assert!(!html.contains(r#"
"#) || !html.contains(r#"
"#)); } async fn rocket() -> Rocket { @@ -542,11 +603,7 @@ This is the end of the markdown let req = client.get("/docs/not_a_doc"); let rsp = req.dispatch().await; - assert!( - rsp.status() == Status::NotFound, - "Returned status {:?}", - rsp.status() - ); + assert!(rsp.status() == Status::NotFound, "Returned status {:?}", rsp.status()); } // Test backend for line highlights and line numbers added @@ -594,17 +651,10 @@ This is the end of the markdown let mut plugins = ComrakPlugins::default(); let headings = crate::utils::markdown::MarkdownHeadings::new(); plugins.render.heading_adapter = Some(&headings); - plugins.render.codefence_syntax_highlighter = - Some(&crate::utils::markdown::SyntaxHighlighter {}); + plugins.render.codefence_syntax_highlighter = Some(&crate::utils::markdown::SyntaxHighlighter {}); let mut html = vec![]; - format_html_with_plugins( - root, - &crate::utils::markdown::options(), - &mut html, - &plugins, - ) - .unwrap(); + format_html_with_plugins(root, &crate::utils::markdown::options(), &mut html, &plugins).unwrap(); let html = String::from_utf8(html).unwrap(); println!("expected: {}", expected); @@ -612,13 +662,8 @@ This is the end of the markdown println!("response: {}", html); assert!( - html.chars() - .filter(|c| !c.is_whitespace()) - .collect::() - == expected - .chars() - .filter(|c| !c.is_whitespace()) - .collect::() + html.chars().filter(|c| !c.is_whitespace()).collect::() + == expected.chars().filter(|c| !c.is_whitespace()).collect::() ) } } diff --git a/pgml-dashboard/src/components/chatbot/mod.rs b/pgml-dashboard/src/components/chatbot/mod.rs index 4cb2f872c..6c9b01b19 100644 --- a/pgml-dashboard/src/components/chatbot/mod.rs +++ b/pgml-dashboard/src/components/chatbot/mod.rs @@ -42,16 +42,8 @@ const EXAMPLE_QUESTIONS: ExampleQuestions = [ ]; const KNOWLEDGE_BASES_WITH_LOGO: [KnowledgeBaseWithLogo; 4] = [ - KnowledgeBaseWithLogo::new( - "postgresml", - "PostgresML", - "/dashboard/static/images/owl_gradient.svg", - ), - KnowledgeBaseWithLogo::new( - "pytorch", - "PyTorch", - "/dashboard/static/images/logos/pytorch.svg", - ), + KnowledgeBaseWithLogo::new("postgresml", "PostgresML", "/dashboard/static/images/owl_gradient.svg"), + KnowledgeBaseWithLogo::new("pytorch", "PyTorch", "/dashboard/static/images/logos/pytorch.svg"), KnowledgeBaseWithLogo::new("rust", "Rust", "/dashboard/static/images/logos/rust.svg"), KnowledgeBaseWithLogo::new( "postgresql", @@ -107,12 +99,7 @@ struct ChatbotBrain { } impl ChatbotBrain { - const fn new( - id: &'static str, - provider: &'static str, - model: &'static str, - logo: &'static str, - ) -> Self { + const fn new(id: &'static str, provider: &'static str, model: &'static str, logo: &'static str) -> Self { Self { id, provider, diff --git a/pgml-dashboard/src/components/dropdown/mod.rs b/pgml-dashboard/src/components/dropdown/mod.rs index 77f71b1ce..734b2eb8a 100644 --- a/pgml-dashboard/src/components/dropdown/mod.rs +++ b/pgml-dashboard/src/components/dropdown/mod.rs @@ -54,10 +54,7 @@ impl Dropdown { } pub fn nav(links: Vec) -> Self { - let binding = links - .iter() - .filter(|link| link.active) - .collect::>(); + let binding = links.iter().filter(|link| link.active).collect::>(); let active = binding.first(); let value = if let Some(active) = active { diff --git a/pgml-dashboard/src/components/inputs/select/mod.rs b/pgml-dashboard/src/components/inputs/select/mod.rs index 9e6d33c1e..7d6fdb5ce 100644 --- a/pgml-dashboard/src/components/inputs/select/mod.rs +++ b/pgml-dashboard/src/components/inputs/select/mod.rs @@ -31,11 +31,7 @@ impl Select { name: "input_name".to_owned(), ..Default::default() } - .options(vec![ - "option1".to_owned(), - "option2".to_owned(), - "option3".to_owned(), - ]) + .options(vec!["option1".to_owned(), "option2".to_owned(), "option3".to_owned()]) } pub fn options(mut self, values: Vec) -> Self { diff --git a/pgml-dashboard/src/components/layouts/head/mod.rs b/pgml-dashboard/src/components/layouts/head/mod.rs index debe33496..e42d12e79 100644 --- a/pgml-dashboard/src/components/layouts/head/mod.rs +++ b/pgml-dashboard/src/components/layouts/head/mod.rs @@ -9,6 +9,7 @@ pub struct Head { pub image: Option, pub preloads: Vec, pub context: Option, + pub canonical: Option, } impl Head { @@ -31,6 +32,11 @@ impl Head { self } + pub fn canonical(mut self, canonical: &str) -> Head { + self.canonical = Some(canonical.to_owned()); + self + } + pub fn image(mut self, image: &str) -> Head { self.image = Some(image.to_owned()); self diff --git a/pgml-dashboard/src/components/layouts/head/template.html b/pgml-dashboard/src/components/layouts/head/template.html index e0b36d896..0f9b09dae 100644 --- a/pgml-dashboard/src/components/layouts/head/template.html +++ b/pgml-dashboard/src/components/layouts/head/template.html @@ -49,7 +49,11 @@ } } - + + <% if canonical.is_some() { %> + + <% } %> + "> diff --git a/pgml-dashboard/src/components/postgres_logo/mod.rs b/pgml-dashboard/src/components/postgres_logo/mod.rs index 8f5c63aa9..fdeef1100 100644 --- a/pgml-dashboard/src/components/postgres_logo/mod.rs +++ b/pgml-dashboard/src/components/postgres_logo/mod.rs @@ -9,9 +9,7 @@ pub struct PostgresLogo { impl PostgresLogo { pub fn new(link: &str) -> PostgresLogo { - PostgresLogo { - link: link.to_owned(), - } + PostgresLogo { link: link.to_owned() } } } diff --git a/pgml-dashboard/src/components/star/mod.rs b/pgml-dashboard/src/components/star/mod.rs index 3689d028f..d84a2db45 100644 --- a/pgml-dashboard/src/components/star/mod.rs +++ b/pgml-dashboard/src/components/star/mod.rs @@ -14,14 +14,8 @@ pub struct Star { static SVGS: Lazy> = Lazy::new(|| { let mut map = HashMap::new(); - map.insert( - "green", - include_str!("../../../static/images/icons/stars/green.svg"), - ); - map.insert( - "party", - include_str!("../../../static/images/icons/stars/party.svg"), - ); + map.insert("green", include_str!("../../../static/images/icons/stars/green.svg")); + map.insert("party", include_str!("../../../static/images/icons/stars/party.svg")); map.insert( "give_it_a_spin", include_str!("../../../static/images/icons/stars/give_it_a_spin.svg"), diff --git a/pgml-dashboard/src/components/stimulus/stimulus_target/mod.rs b/pgml-dashboard/src/components/stimulus/stimulus_target/mod.rs index 7b751aee3..dcc00698b 100644 --- a/pgml-dashboard/src/components/stimulus/stimulus_target/mod.rs +++ b/pgml-dashboard/src/components/stimulus/stimulus_target/mod.rs @@ -8,9 +8,7 @@ pub struct StimulusTarget { impl StimulusTarget { pub fn new() -> Self { - Self { - ..Default::default() - } + Self { ..Default::default() } } pub fn controller(mut self, controller: &str) -> Self { @@ -27,9 +25,7 @@ impl StimulusTarget { impl Render for StimulusTarget { fn render(&self, b: &mut Buffer) -> Result<(), sailfish::RenderError> { match (self.controller.as_ref(), self.name.as_ref()) { - (Some(controller), Some(name)) => { - format!("data-{}-target=\"{}\"", controller, name).render(b) - } + (Some(controller), Some(name)) => format!("data-{}-target=\"{}\"", controller, name).render(b), _ => String::new().render(b), } } diff --git a/pgml-dashboard/src/fairings.rs b/pgml-dashboard/src/fairings.rs index 6107809db..ca818df75 100644 --- a/pgml-dashboard/src/fairings.rs +++ b/pgml-dashboard/src/fairings.rs @@ -34,9 +34,7 @@ impl Fairing for RequestMonitor { } async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) { - let start = request - .local_cache(|| RequestMonitorStart(std::time::Instant::now())) - .0; + let start = request.local_cache(|| RequestMonitorStart(std::time::Instant::now())).0; let elapsed = start.elapsed().as_micros() as f32 / 1000.0; let status = response.status().code; let method = request.method().as_str(); diff --git a/pgml-dashboard/src/guards.rs b/pgml-dashboard/src/guards.rs index b16da5cdc..5b60479fa 100644 --- a/pgml-dashboard/src/guards.rs +++ b/pgml-dashboard/src/guards.rs @@ -33,8 +33,7 @@ impl Cluster { .min_connections(min_connections) .after_connect(|conn, _meta| { Box::pin(async move { - conn.execute("SET application_name = 'pgml_dashboard';") - .await?; + conn.execute("SET application_name = 'pgml_dashboard';").await?; Ok(()) }) }) @@ -47,87 +46,39 @@ impl Cluster { user: models::User::default(), cluster: models::Cluster::default(), dropdown_nav: StaticNav { - links: vec![ - StaticNavLink::new("Local".to_string(), "/dashboard".to_string()) - .active(true), - ], + links: vec![StaticNavLink::new("Local".to_string(), "/dashboard".to_string()).active(true)], }, account_management_nav: StaticNav { links: vec![ StaticNavLink::new("Notebooks".to_string(), "/dashboard".to_string()), - StaticNavLink::new( - "Projects".to_string(), - "/dashboard?tab=Projects".to_string(), - ), - StaticNavLink::new( - "Models".to_string(), - "/dashboard?tab=Models".to_string(), - ), - StaticNavLink::new( - "Snapshots".to_string(), - "/dashboard?tab=Snapshots".to_string(), - ), - StaticNavLink::new( - "Upload data".to_string(), - "/dashboard?tab=Upload_Data".to_string(), - ), - StaticNavLink::new( - "PostgresML.org".to_string(), - "https://postgresml.org".to_string(), - ), + StaticNavLink::new("Projects".to_string(), "/dashboard?tab=Projects".to_string()), + StaticNavLink::new("Models".to_string(), "/dashboard?tab=Models".to_string()), + StaticNavLink::new("Snapshots".to_string(), "/dashboard?tab=Snapshots".to_string()), + StaticNavLink::new("Upload data".to_string(), "/dashboard?tab=Upload_Data".to_string()), + StaticNavLink::new("PostgresML.org".to_string(), "https://postgresml.org".to_string()), ], }, upper_left_nav: StaticNav { links: vec![ - StaticNavLink::new( - "Notebooks".to_string(), - "/dashboard?tab=Notebooks".to_string(), - ) - .icon("add_notes") - .active( - uri.is_some() - && (uri.clone().unwrap().starts_with("/dashboard?tab=Notebook") - || uri.clone().unwrap() == "/dashboard"), - ), - StaticNavLink::new( - "Projects".to_string(), - "/dashboard?tab=Projects".to_string(), - ) - .icon("library_add") - .active( - uri.is_some() - && uri.clone().unwrap().starts_with("/dashboard?tab=Project"), - ), - StaticNavLink::new( - "Models".to_string(), - "/dashboard?tab=Models".to_string(), - ) - .icon("space_dashboard") - .active( - uri.is_some() - && uri.clone().unwrap().starts_with("/dashboard?tab=Model"), - ), - StaticNavLink::new( - "Snapshots".to_string(), - "/dashboard?tab=Snapshots".to_string(), - ) - .icon("filter_center_focus") - .active( - uri.is_some() - && uri.clone().unwrap().starts_with("/dashboard?tab=Snapshot"), - ), - StaticNavLink::new( - "Upload data".to_string(), - "/dashboard?tab=Upload_Data".to_string(), - ) - .icon("upload") - .active( - uri.is_some() - && uri - .clone() - .unwrap() - .starts_with("/dashboard?tab=Upload_Data"), - ), + StaticNavLink::new("Notebooks".to_string(), "/dashboard?tab=Notebooks".to_string()) + .icon("add_notes") + .active( + uri.is_some() + && (uri.clone().unwrap().starts_with("/dashboard?tab=Notebook") + || uri.clone().unwrap() == "/dashboard"), + ), + StaticNavLink::new("Projects".to_string(), "/dashboard?tab=Projects".to_string()) + .icon("library_add") + .active(uri.is_some() && uri.clone().unwrap().starts_with("/dashboard?tab=Project")), + StaticNavLink::new("Models".to_string(), "/dashboard?tab=Models".to_string()) + .icon("space_dashboard") + .active(uri.is_some() && uri.clone().unwrap().starts_with("/dashboard?tab=Model")), + StaticNavLink::new("Snapshots".to_string(), "/dashboard?tab=Snapshots".to_string()) + .icon("filter_center_focus") + .active(uri.is_some() && uri.clone().unwrap().starts_with("/dashboard?tab=Snapshot")), + StaticNavLink::new("Upload data".to_string(), "/dashboard?tab=Upload_Data".to_string()) + .icon("upload") + .active(uri.is_some() && uri.clone().unwrap().starts_with("/dashboard?tab=Upload_Data")), ], }, lower_left_nav: StaticNav::default(), diff --git a/pgml-dashboard/src/lib.rs b/pgml-dashboard/src/lib.rs index efbff38a1..3149be444 100644 --- a/pgml-dashboard/src/lib.rs +++ b/pgml-dashboard/src/lib.rs @@ -192,17 +192,12 @@ pub async fn project_get(cluster: ConnectedCluster<'_>, id: i64) -> Result")] -pub async fn notebook_index( - cluster: ConnectedCluster<'_>, - new: Option<&str>, -) -> Result { +pub async fn notebook_index(cluster: ConnectedCluster<'_>, new: Option<&str>) -> Result { Ok(ResponseOk( templates::Notebooks { notebooks: models::Notebook::all(cluster.pool()).await?, @@ -214,47 +209,30 @@ pub async fn notebook_index( } #[post("/notebooks", data = "")] -pub async fn notebook_create( - cluster: &Cluster, - data: Form>, -) -> Result { +pub async fn notebook_create(cluster: &Cluster, data: Form>) -> Result { let notebook = crate::models::Notebook::create(cluster.pool(), data.name).await?; models::Cell::create(cluster.pool(), ¬ebook, models::CellType::Sql as i32, "").await?; - Ok(Redirect::to(format!( - "/dashboard?tab=Notebook&id={}", - notebook.id - ))) + Ok(Redirect::to(format!("/dashboard?tab=Notebook&id={}", notebook.id))) } #[get("/notebooks/")] -pub async fn notebook_get( - cluster: ConnectedCluster<'_>, - notebook_id: i64, -) -> Result { +pub async fn notebook_get(cluster: ConnectedCluster<'_>, notebook_id: i64) -> Result { let notebook = models::Notebook::get_by_id(cluster.pool(), notebook_id).await?; let cells = notebook.cells(cluster.pool()).await?; Ok(ResponseOk( - templates::Notebook { cells, notebook } - .render_once() - .unwrap(), + templates::Notebook { cells, notebook }.render_once().unwrap(), )) } #[post("/notebooks//reset")] -pub async fn notebook_reset( - cluster: ConnectedCluster<'_>, - notebook_id: i64, -) -> Result { +pub async fn notebook_reset(cluster: ConnectedCluster<'_>, notebook_id: i64) -> Result { let notebook = models::Notebook::get_by_id(cluster.pool(), notebook_id).await?; notebook.reset(cluster.pool()).await?; - Ok(Redirect::to(format!( - "/dashboard/notebooks/{}", - notebook_id - ))) + Ok(Redirect::to(format!("/dashboard/notebooks/{}", notebook_id))) } #[post("/notebooks//cell", data = "")] @@ -264,22 +242,14 @@ pub async fn cell_create( cell: Form>, ) -> Result { let notebook = models::Notebook::get_by_id(cluster.pool(), notebook_id).await?; - let mut cell = models::Cell::create( - cluster.pool(), - ¬ebook, - cell.cell_type.parse::()?, - cell.contents, - ) - .await?; + let mut cell = + models::Cell::create(cluster.pool(), ¬ebook, cell.cell_type.parse::()?, cell.contents).await?; if !cell.contents.is_empty() { cell.render(cluster.pool()).await?; } - Ok(Redirect::to(format!( - "/dashboard/notebooks/{}", - notebook_id - ))) + Ok(Redirect::to(format!("/dashboard/notebooks/{}", notebook_id))) } #[post("/notebooks//reorder", data = "")] @@ -301,18 +271,11 @@ pub async fn notebook_reorder( transaction.commit().await?; - Ok(Redirect::to(format!( - "/dashboard/notebooks/{}", - notebook_id - ))) + Ok(Redirect::to(format!("/dashboard/notebooks/{}", notebook_id))) } #[get("/notebooks//cell/")] -pub async fn cell_get( - cluster: ConnectedCluster<'_>, - notebook_id: i64, - cell_id: i64, -) -> Result { +pub async fn cell_get(cluster: ConnectedCluster<'_>, notebook_id: i64, cell_id: i64) -> Result { let notebook = models::Notebook::get_by_id(cluster.pool(), notebook_id).await?; let cell = models::Cell::get_by_id(cluster.pool(), cell_id).await?; @@ -329,11 +292,7 @@ pub async fn cell_get( } #[post("/notebooks//cell//cancel")] -pub async fn cell_cancel( - cluster: ConnectedCluster<'_>, - notebook_id: i64, - cell_id: i64, -) -> Result { +pub async fn cell_cancel(cluster: ConnectedCluster<'_>, notebook_id: i64, cell_id: i64) -> Result { let cell = models::Cell::get_by_id(cluster.pool(), cell_id).await?; cell.cancel(cluster.pool()).await?; Ok(Redirect::to(format!( @@ -352,12 +311,8 @@ pub async fn cell_edit( let notebook = models::Notebook::get_by_id(cluster.pool(), notebook_id).await?; let mut cell = models::Cell::get_by_id(cluster.pool(), cell_id).await?; - cell.update( - cluster.pool(), - data.cell_type.parse::()?, - data.contents, - ) - .await?; + cell.update(cluster.pool(), data.cell_type.parse::()?, data.contents) + .await?; debug!("Rendering cell id={}", cell.id); cell.render(cluster.pool()).await?; @@ -397,11 +352,7 @@ pub async fn cell_trigger_edit( } #[post("/notebooks//cell//play")] -pub async fn cell_play( - cluster: ConnectedCluster<'_>, - notebook_id: i64, - cell_id: i64, -) -> Result { +pub async fn cell_play(cluster: ConnectedCluster<'_>, notebook_id: i64, cell_id: i64) -> Result { let notebook = models::Notebook::get_by_id(cluster.pool(), notebook_id).await?; let mut cell = models::Cell::get_by_id(cluster.pool(), cell_id).await?; cell.render(cluster.pool()).await?; @@ -419,11 +370,7 @@ pub async fn cell_play( } #[post("/notebooks//cell//remove")] -pub async fn cell_remove( - cluster: ConnectedCluster<'_>, - notebook_id: i64, - cell_id: i64, -) -> Result { +pub async fn cell_remove(cluster: ConnectedCluster<'_>, notebook_id: i64, cell_id: i64) -> Result { let notebook = models::Notebook::get_by_id(cluster.pool(), notebook_id).await?; let cell = models::Cell::get_by_id(cluster.pool(), cell_id).await?; let bust_cache = std::time::SystemTime::now() @@ -442,11 +389,7 @@ pub async fn cell_remove( } #[post("/notebooks//cell//delete")] -pub async fn cell_delete( - cluster: ConnectedCluster<'_>, - notebook_id: i64, - cell_id: i64, -) -> Result { +pub async fn cell_delete(cluster: ConnectedCluster<'_>, notebook_id: i64, cell_id: i64) -> Result { let _notebook = models::Notebook::get_by_id(cluster.pool(), notebook_id).await?; let cell = models::Cell::get_by_id(cluster.pool(), cell_id).await?; @@ -518,9 +461,7 @@ pub async fn models_get(cluster: ConnectedCluster<'_>, id: i64) -> Result) -> Result { let snapshots = models::Snapshot::all(cluster.pool()).await?; - Ok(ResponseOk( - templates::Snapshots { snapshots }.render_once().unwrap(), - )) + Ok(ResponseOk(templates::Snapshots { snapshots }.render_once().unwrap())) } #[get("/snapshots/")] @@ -560,12 +501,7 @@ pub async fn deployments_index(cluster: ConnectedCluster<'_>) -> Result")] pub async fn uploaded_index(cluster: ConnectedCluster<'_>, table_name: &str) -> ResponseOk { - let sql = templates::Sql::new( - cluster.pool(), - &format!("SELECT * FROM {} LIMIT 10", table_name), - ) - .await - .unwrap(); + let sql = templates::Sql::new(cluster.pool(), &format!("SELECT * FROM {} LIMIT 10", table_name)) + .await + .unwrap(); ResponseOk( templates::Uploaded { table_name: table_name.to_string(), @@ -636,11 +569,7 @@ pub async fn uploaded_index(cluster: ConnectedCluster<'_>, table_name: &str) -> } #[get("/?&")] -pub async fn dashboard( - cluster: ConnectedCluster<'_>, - tab: Option<&str>, - id: Option, -) -> Result { +pub async fn dashboard(cluster: ConnectedCluster<'_>, tab: Option<&str>, id: Option) -> Result { let mut layout = crate::templates::WebAppBase::new("Dashboard", &cluster.inner.context); let mut breadcrumbs = vec![NavLink::new("Dashboard", "/dashboard")]; @@ -672,13 +601,8 @@ pub async fn dashboard( "Project" => { let project = models::Project::get_by_id(cluster.pool(), id.unwrap()).await?; breadcrumbs.push(NavLink::new("Projects", "/dashboard?tab=Projects")); - breadcrumbs.push( - NavLink::new( - &project.name, - &format!("/dashboard?tab=Project&id={}", project.id), - ) - .active(), - ); + breadcrumbs + .push(NavLink::new(&project.name, &format!("/dashboard?tab=Project&id={}", project.id)).active()); } "Models" => { @@ -694,13 +618,7 @@ pub async fn dashboard( &project.name, &format!("/dashboard?tab=Project&id={}", project.id), )); - breadcrumbs.push( - NavLink::new( - &model.algorithm, - &format!("/dashboard?tab=Model&id={}", model.id), - ) - .active(), - ); + breadcrumbs.push(NavLink::new(&model.algorithm, &format!("/dashboard?tab=Model&id={}", model.id)).active()); } "Snapshots" => { @@ -756,11 +674,7 @@ pub async fn dashboard( "Model" => vec![tabs::Tab { name: "Model", - content: ModelTab { - model_id: id.unwrap(), - } - .render_once() - .unwrap(), + content: ModelTab { model_id: id.unwrap() }.render_once().unwrap(), }], "Snapshots" => vec![tabs::Tab { @@ -786,9 +700,7 @@ pub async fn dashboard( let nav_tabs = tabs::Tabs::new(tabs, Some("Notebooks"), Some(tab))?; - Ok(ResponseOk( - layout.render(templates::Dashboard { tabs: nav_tabs }), - )) + Ok(ResponseOk(layout.render(templates::Dashboard { tabs: nav_tabs }))) } #[get("/playground")] @@ -798,12 +710,7 @@ pub async fn playground(cluster: &Cluster) -> Result { } #[get("/notifications/remove_banner?&")] -pub fn remove_banner( - id: String, - alert: bool, - cookies: &CookieJar<'_>, - context: &Cluster, -) -> ResponseOk { +pub fn remove_banner(id: String, alert: bool, cookies: &CookieJar<'_>, context: &Cluster) -> ResponseOk { let mut viewed = Notifications::get_viewed(cookies); viewed.push(id); @@ -814,9 +721,7 @@ pub fn remove_banner( if alert { notifications .into_iter() - .filter(|n: &&Notification| -> bool { - Notification::is_alert(&n.level) && !viewed.contains(&n.id) - }) + .filter(|n: &&Notification| -> bool { Notification::is_alert(&n.level) && !viewed.contains(&n.id) }) .next() } else { notifications @@ -831,17 +736,9 @@ pub fn remove_banner( }; if alert { - return ResponseOk( - AlertBanner::from_notification(notification) - .render_once() - .unwrap(), - ); + return ResponseOk(AlertBanner::from_notification(notification).render_once().unwrap()); } else { - return ResponseOk( - FeatureBanner::from_notification(notification) - .render_once() - .unwrap(), - ); + return ResponseOk(FeatureBanner::from_notification(notification).render_once().unwrap()); } } diff --git a/pgml-dashboard/src/main.rs b/pgml-dashboard/src/main.rs index e8161a452..f09b21d8b 100644 --- a/pgml-dashboard/src/main.rs +++ b/pgml-dashboard/src/main.rs @@ -1,8 +1,6 @@ use log::{error, info, warn}; -use rocket::{ - catch, catchers, fs::FileServer, get, http::Status, request::Request, response::Redirect, -}; +use rocket::{catch, catchers, fs::FileServer, get, http::Status, request::Request, response::Redirect}; use pgml_dashboard::{ guards, @@ -33,10 +31,7 @@ async fn not_found_handler(_status: Status, _request: &Request<'_>) -> Response } #[catch(default)] -async fn error_catcher( - status: Status, - request: &Request<'_>, -) -> Result { +async fn error_catcher(status: Status, request: &Request<'_>) -> Result { Err(responses::Error(anyhow::anyhow!( "{} {}\n{:?}", status.code, @@ -59,8 +54,7 @@ async fn configure_reporting() -> Option { log::set_boxed_logger(Box::new(logger)).unwrap(); log::set_max_level(level); - let name = - sentry::release_name!().unwrap_or_else(|| std::borrow::Cow::Borrowed("cloud2")); + let name = sentry::release_name!().unwrap_or_else(|| std::borrow::Cow::Borrowed("cloud2")); let sha = env!("GIT_SHA"); let release = format!("{name}+{sha}"); let result = sentry::init(( @@ -111,10 +105,7 @@ async fn main() { .mount("/dashboard", pgml_dashboard::routes()) .mount("/", pgml_dashboard::api::routes()) .mount("/", rocket::routes![pgml_dashboard::playground]) - .register( - "/", - catchers![error_catcher, not_authorized_catcher, not_found_handler], - ) + .register("/", catchers![error_catcher, not_authorized_catcher, not_found_handler]) .attach(pgml_dashboard::fairings::RequestMonitor::new()) .ignite() .await @@ -138,9 +129,7 @@ mod test { async fn rocket() -> Rocket { dotenv::dotenv().ok(); - pgml_dashboard::migrate(Cluster::default(None).pool()) - .await - .unwrap(); + pgml_dashboard::migrate(Cluster::default(None).pool()).await.unwrap(); rocket::build() .manage(markdown::SearchIndex::open().unwrap()) @@ -290,7 +279,10 @@ mod test { #[rocket::async_test] async fn test_blogs() { let client = Client::tracked(rocket().await).await.unwrap(); - let response = client.get("/blog/postgresml-raises-usd4.7m-to-launch-serverless-ai-application-databases-based-on-postgres").dispatch().await; + let response = client + .get("/blog/postgresml-raises-usd4.7m-to-launch-serverless-ai-application-databases-based-on-postgres") + .dispatch() + .await; assert_eq!(response.status().code, 200); } } diff --git a/pgml-dashboard/src/models.rs b/pgml-dashboard/src/models.rs index 8896b9fae..c26ca363f 100644 --- a/pgml-dashboard/src/models.rs +++ b/pgml-dashboard/src/models.rs @@ -187,12 +187,7 @@ pub struct Cell { } impl Cell { - pub async fn create( - pool: &PgPool, - notebook: &Notebook, - cell_type: i32, - contents: &str, - ) -> anyhow::Result { + pub async fn create(pool: &PgPool, notebook: &Notebook, cell_type: i32, contents: &str) -> anyhow::Result { Ok(sqlx::query_as!( Cell, " @@ -249,12 +244,7 @@ impl Cell { .await?) } - pub async fn update( - &mut self, - pool: &PgPool, - cell_type: i32, - contents: &str, - ) -> anyhow::Result<()> { + pub async fn update(&mut self, pool: &PgPool, cell_type: i32, contents: &str) -> anyhow::Result<()> { self.cell_type = cell_type; self.contents = contents.to_string(); @@ -296,11 +286,7 @@ impl Cell { .await?) } - pub async fn reorder( - self, - pool: impl sqlx::PgExecutor<'_>, - cell_number: i32, - ) -> anyhow::Result { + pub async fn reorder(self, pool: impl sqlx::PgExecutor<'_>, cell_number: i32) -> anyhow::Result { Ok(sqlx::query_as!( Cell, " @@ -348,11 +334,7 @@ impl Cell { let (rendering, execution_time) = match cell_type { CellType::Sql => { - let queries: Vec<&str> = self - .contents - .split(';') - .filter(|q| !q.trim().is_empty()) - .collect(); + let queries: Vec<&str> = self.contents.split(';').filter(|q| !q.trim().is_empty()).collect(); let mut rendering = String::new(); let mut total_execution_duration = std::time::Duration::default(); @@ -678,18 +660,12 @@ impl Snapshot { pub fn rows(&self) -> Option { match self.analysis.as_ref() { - Some(analysis) => analysis - .get("samples") - .map(|samples| samples.as_f64().unwrap() as i64), + Some(analysis) => analysis.get("samples").map(|samples| samples.as_f64().unwrap() as i64), None => None, } } - pub async fn samples( - &self, - pool: &PgPool, - rows: i64, - ) -> anyhow::Result>> { + pub async fn samples(&self, pool: &PgPool, rows: i64) -> anyhow::Result>> { let mut samples = HashMap::new(); if self.exists { @@ -722,12 +698,9 @@ impl Snapshot { pub fn columns(&self) -> Option>> { match self.columns.as_ref() { - Some(columns) => columns.as_array().map(|columns| { - columns - .iter() - .map(|column| column.as_object().unwrap()) - .collect() - }), + Some(columns) => columns + .as_array() + .map(|columns| columns.iter().map(|column| column.as_object().unwrap()).collect()), None => None, } @@ -793,9 +766,7 @@ impl Snapshot { // 2.2+ None => { let columns = self.columns().unwrap(); - let column = columns - .iter() - .find(|column| column["name"].as_str().unwrap() == name); + let column = columns.iter().find(|column| column["name"].as_str().unwrap() == name); match column { Some(column) => column .get("statistics") @@ -825,10 +796,7 @@ pub struct Deployment { } impl Deployment { - pub async fn get_by_project_id( - pool: &PgPool, - project_id: i64, - ) -> anyhow::Result> { + pub async fn get_by_project_id(pool: &PgPool, project_id: i64) -> anyhow::Result> { Ok(sqlx::query_as!( Deployment, "SELECT @@ -904,12 +872,7 @@ impl UploadedFile { .await?) } - pub async fn upload( - &mut self, - pool: &PgPool, - file: &std::path::Path, - headers: bool, - ) -> anyhow::Result<()> { + pub async fn upload(&mut self, pool: &PgPool, file: &std::path::Path, headers: bool) -> anyhow::Result<()> { // Open the temp file. let mut reader = tokio::io::BufReader::new(tokio::fs::File::open(file).await?); diff --git a/pgml-dashboard/src/responses.rs b/pgml-dashboard/src/responses.rs index fe7574124..cec755200 100644 --- a/pgml-dashboard/src/responses.rs +++ b/pgml-dashboard/src/responses.rs @@ -81,8 +81,7 @@ impl<'r> response::Responder<'r, 'r> for Response { let body = match self.body { Some(body) => body, None => match self.status.code { - 404 => templates::Layout::new("Internal Server Error", None) - .render(templates::NotFound {}), + 404 => templates::Layout::new("Internal Server Error", None).render(templates::NotFound {}), _ => "".into(), }, }; @@ -133,8 +132,7 @@ impl<'r> response::Responder<'r, 'r> for Error { "".into() }; - let body = templates::Layout::new("Internal Server Error", None) - .render(templates::Error { error }); + let body = templates::Layout::new("Internal Server Error", None).render(templates::Error { error }); response::Response::build_from(body.respond_to(request)?) .header(ContentType::new("text", "html")) diff --git a/pgml-dashboard/src/templates/mod.rs b/pgml-dashboard/src/templates/mod.rs index 6d9a6c4fd..07b5e7488 100644 --- a/pgml-dashboard/src/templates/mod.rs +++ b/pgml-dashboard/src/templates/mod.rs @@ -44,9 +44,7 @@ pub struct Layout { impl Layout { pub fn new(title: &str, context: Option<&crate::guards::Cluster>) -> Self { let head = match context.as_ref() { - Some(context) => Head::new() - .title(title) - .context(&context.context.head_items), + Some(context) => Head::new().title(title).context(&context.context.head_items), None => Head::new().title(title), }; @@ -68,6 +66,11 @@ impl Layout { self } + pub fn canonical(&mut self, canonical: &str) -> &mut Self { + self.head.canonical = Some(canonical.to_string()); + self + } + pub fn content(&mut self, content: &str) -> &mut Self { self.content = Some(content.to_owned()); self @@ -346,10 +349,7 @@ impl Sql { let (hour, minute, second, milli) = value.as_hms_milli(); let (year, month, day) = value.to_calendar_date(); - format!( - "{}-{}-{} {}:{}:{}.{}", - year, month, day, hour, minute, second, milli - ) + format!("{}-{}-{} {}:{}:{}.{}", year, month, day, hour, minute, second, milli) } "MONEY" => { diff --git a/pgml-dashboard/src/utils/config.rs b/pgml-dashboard/src/utils/config.rs index 9f76eaabd..fdb31ad57 100644 --- a/pgml-dashboard/src/utils/config.rs +++ b/pgml-dashboard/src/utils/config.rs @@ -74,8 +74,7 @@ impl Config { render_errors: env_is_set("RENDER_ERRORS") || dev_mode, deployment: env_string_default("DEPLOYMENT", "localhost"), signup_url, - standalone_dashboard: !cargo_manifest_dir.contains("deps") - && !cargo_manifest_dir.contains("cloud2"), + standalone_dashboard: !cargo_manifest_dir.contains("deps") && !cargo_manifest_dir.contains("cloud2"), github_stars, css_extension, js_extension, diff --git a/pgml-dashboard/src/utils/cookies.rs b/pgml-dashboard/src/utils/cookies.rs index af791b0da..02f102205 100644 --- a/pgml-dashboard/src/utils/cookies.rs +++ b/pgml-dashboard/src/utils/cookies.rs @@ -12,10 +12,7 @@ impl Notifications { pub fn get_viewed(cookies: &CookieJar<'_>) -> Vec { let viewed = match cookies.get_private("session") { Some(session) => { - match serde_json::from_str::(session.value()).unwrap() - ["notifications"] - .as_array() - { + match serde_json::from_str::(session.value()).unwrap()["notifications"].as_array() { Some(items) => items .into_iter() .map(|x| x.as_str().unwrap().to_string()) diff --git a/pgml-dashboard/src/utils/datadog.rs b/pgml-dashboard/src/utils/datadog.rs index f85d64c26..e7a832388 100644 --- a/pgml-dashboard/src/utils/datadog.rs +++ b/pgml-dashboard/src/utils/datadog.rs @@ -74,11 +74,7 @@ pub async fn timing(metric: &str, millis: f32, tags: Option<&HashMap( - metric: &str, - tags: Option<&HashMap>, - f: impl FnOnce() -> T, -) -> T { +pub async fn time(metric: &str, tags: Option<&HashMap>, f: impl FnOnce() -> T) -> T { let start = Instant::now(); let result = f(); send( diff --git a/pgml-dashboard/src/utils/markdown.rs b/pgml-dashboard/src/utils/markdown.rs index fb0557aa2..0cf172289 100644 --- a/pgml-dashboard/src/utils/markdown.rs +++ b/pgml-dashboard/src/utils/markdown.rs @@ -601,9 +601,7 @@ impl Admonition { impl From<&str> for Admonition { fn from(utf8: &str) -> Admonition { - let (class, icon, title) = if utf8.starts_with("!!! info") - || utf8.starts_with(r#"{% hint style="info" %}"#) - { + let (class, icon, title) = if utf8.starts_with("!!! info") || utf8.starts_with(r#"{% hint style="info" %}"#) { ("admonition-info", "help", "Info") } else if utf8.starts_with("!!! note") { ("admonition-note", "priority_high", "Note") @@ -615,22 +613,17 @@ impl From<&str> for Admonition { ("admonition-question", "help", "Question") } else if utf8.starts_with("!!! example") { ("admonition-example", "code", "Example") - } else if utf8.starts_with("!!! success") - || utf8.starts_with(r#"{% hint style="success" %}"#) - { + } else if utf8.starts_with("!!! success") || utf8.starts_with(r#"{% hint style="success" %}"#) { ("admonition-success", "check_circle", "Success") } else if utf8.starts_with("!!! quote") { ("admonition-quote", "format_quote", "Quote") } else if utf8.starts_with("!!! bug") { ("admonition-bug", "bug_report", "Bug") - } else if utf8.starts_with("!!! warning") - || utf8.starts_with(r#"{% hint style="warning" %}"#) - { + } else if utf8.starts_with("!!! warning") || utf8.starts_with(r#"{% hint style="warning" %}"#) { ("admonition-warning", "warning", "Warning") } else if utf8.starts_with("!!! fail") { ("admonition-fail", "dangerous", "Fail") - } else if utf8.starts_with("!!! danger") || utf8.starts_with(r#"{% hint style="danger" %}"#) - { + } else if utf8.starts_with("!!! danger") || utf8.starts_with(r#"{% hint style="danger" %}"#) { ("admonition-danger", "gpp_maybe", "Danger") } else { ("admonition-generic", "", "") @@ -796,13 +789,12 @@ pub fn mkdocs<'a>(root: &'a AstNode<'a>, arena: &'a Arena>) -> anyho let tab = Tab::new(text.replace("=== ", "").replace('\"', "")); if tabs.is_empty() { - let n = - arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( - r#" + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + r#" ".to_string()), - )))); + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + "".to_string(), + ))))); parent.insert_after(n); parent.detach(); parent = n; - let n = arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline(r#"
"#.to_string()), - )))); + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + r#"
"#.to_string(), + ))))); parent.insert_after(n); parent = n; for tab in tabs.iter() { - let r = arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline(format!( - r#" + let r = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline(format!( + r#"
"#, - active = if tab.active { "show active" } else { "" }, - id = tab.id - )), - )))); + active = if tab.active { "show active" } else { "" }, + id = tab.id + )))))); for child in tab.children.iter() { r.append(child); @@ -869,17 +859,17 @@ pub fn mkdocs<'a>(root: &'a AstNode<'a>, arena: &'a Arena>) -> anyho parent.append(r); parent = r; - let n = arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline(r#"
"#.to_string()), - )))); + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + r#"
"#.to_string(), + ))))); parent.insert_after(n); parent = n; } - parent.insert_after(arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline(r#"
"#.to_string()), - ))))); + parent.insert_after(arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + r#""#.to_string(), + )))))); tabs.clear(); node.detach(); @@ -901,13 +891,12 @@ pub fn mkdocs<'a>(root: &'a AstNode<'a>, arena: &'a Arena>) -> anyho let tab = Tab::new(text.replace("{% tab title=\"", "").replace("\" %}", "")); if tabs.is_empty() { - let n = - arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( - r#" + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + r#" ".to_string()), - )))); + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + "".to_string(), + ))))); parent.insert_after(n); parent.detach(); parent = n; - let n = arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline(r#"
"#.to_string()), - )))); + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + r#"
"#.to_string(), + ))))); parent.insert_after(n); parent = n; for tab in tabs.iter() { - let r = arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline(format!( - r#" + let r = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline(format!( + r#"
"#, - active = if tab.active { "show active" } else { "" }, - id = tab.id - )), - )))); + active = if tab.active { "show active" } else { "" }, + id = tab.id + )))))); for child in tab.children.iter() { r.append(child); @@ -974,17 +961,17 @@ pub fn mkdocs<'a>(root: &'a AstNode<'a>, arena: &'a Arena>) -> anyho parent.append(r); parent = r; - let n = arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline(r#"
"#.to_string()), - )))); + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + r#"
"#.to_string(), + ))))); parent.insert_after(n); parent = n; } - parent.insert_after(arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline(r#"
"#.to_string()), - ))))); + parent.insert_after(arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + r#""#.to_string(), + )))))); tabs.clear(); node.detach(); @@ -1031,9 +1018,7 @@ pub fn mkdocs<'a>(root: &'a AstNode<'a>, arena: &'a Arena>) -> anyho }; if let Some(html) = code_block.html("code") { - let n = arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline(html), - )))); + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline(html))))); parent.insert_after(n); } @@ -1051,9 +1036,7 @@ pub fn mkdocs<'a>(root: &'a AstNode<'a>, arena: &'a Arena>) -> anyho }; if let Some(html) = code_block.html("results") { - let n = arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline(html), - )))); + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline(html))))); parent.insert_after(n); } @@ -1062,14 +1045,12 @@ pub fn mkdocs<'a>(root: &'a AstNode<'a>, arena: &'a Arena>) -> anyho } else if text.contains("{% content-ref url=") { let url = parser(text.as_ref(), r#"url=""#); - let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( - format!( - r#" "# - .to_string(), - ), - )))); + .to_string(), + ))))); parent.insert_after(n); } }, None => { - let n = arena.alloc(Node::new(RefCell::new(Ast::new( - NodeValue::HtmlInline( - r#" + let n = arena.alloc(Node::new(RefCell::new(Ast::new(NodeValue::HtmlInline( + r#" "# - .to_string(), - ), - )))); + .to_string(), + ))))); parent.insert_after(n); } @@ -1203,10 +1180,8 @@ impl SearchIndex { pub fn documents() -> Vec { // TODO imrpove this .display().to_string() - let guides = glob::glob(&config::cms_dir().join("docs/**/*.md").display().to_string()) - .expect("glob failed"); - let blogs = glob::glob(&config::cms_dir().join("blog/**/*.md").display().to_string()) - .expect("glob failed"); + let guides = glob::glob(&config::cms_dir().join("docs/**/*.md").display().to_string()).expect("glob failed"); + let blogs = glob::glob(&config::cms_dir().join("blog/**/*.md").display().to_string()).expect("glob failed"); guides .chain(blogs) .map(|path| path.expect("glob path failed")) @@ -1294,8 +1269,7 @@ impl SearchIndex { let path = Self::path(); if !path.exists() { - std::fs::create_dir(&path) - .expect("failed to create search_index directory, is the filesystem writable?"); + std::fs::create_dir(&path).expect("failed to create search_index directory, is the filesystem writable?"); } let index = match tantivy::Index::open_in_dir(&path) { @@ -1348,14 +1322,13 @@ impl SearchIndex { // If that's not enough, search using prefix search on the title. if top_docs.len() < 10 { - let query = - match RegexQuery::from_pattern(&format!("{}.*", query_string), title_regex_field) { - Ok(query) => query, - Err(err) => { - warn!("Query regex error: {}", err); - return Ok(Vec::new()); - } - }; + let query = match RegexQuery::from_pattern(&format!("{}.*", query_string), title_regex_field) { + Ok(query) => query, + Err(err) => { + warn!("Query regex error: {}", err); + return Ok(Vec::new()); + } + }; let more_results = searcher.search(&query, &TopDocs::with_limit(10)).unwrap(); top_docs.extend(more_results); diff --git a/pgml-dashboard/src/utils/tabs.rs b/pgml-dashboard/src/utils/tabs.rs index 408eb462a..e7d81099d 100644 --- a/pgml-dashboard/src/utils/tabs.rs +++ b/pgml-dashboard/src/utils/tabs.rs @@ -12,18 +12,10 @@ pub struct Tabs<'a> { } impl<'a> Tabs<'a> { - pub fn new( - tabs: Vec>, - default: Option<&'a str>, - active: Option<&'a str>, - ) -> anyhow::Result { + pub fn new(tabs: Vec>, default: Option<&'a str>, active: Option<&'a str>) -> anyhow::Result { let default = match default { Some(default) => default, - _ => { - tabs.get(0) - .ok_or(anyhow!("There must be at least one tab."))? - .name - } + _ => tabs.get(0).ok_or(anyhow!("There must be at least one tab."))?.name, }; let active = active @@ -34,10 +26,6 @@ impl<'a> Tabs<'a> { }) .unwrap_or(default); - Ok(Tabs { - tabs, - default, - active, - }) + Ok(Tabs { tabs, default, active }) } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy