From 227175921f1ca2ea2161cac37fe81434a5d41e9b Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Fri, 15 Mar 2024 09:20:29 -0700 Subject: [PATCH 1/3] Prep for saving events --- pgml-dashboard/src/api/cms.rs | 28 +++-- pgml-dashboard/src/forms.rs | 6 + pgml-dashboard/src/templates/docs.rs | 1 + pgml-dashboard/src/utils/markdown.rs | 27 +++-- pgml-dashboard/static/js/search.js | 105 +++++++++++------- .../templates/components/search.html | 6 +- 6 files changed, 107 insertions(+), 66 deletions(-) diff --git a/pgml-dashboard/src/api/cms.rs b/pgml-dashboard/src/api/cms.rs index 0a58f3b33..53964c29f 100644 --- a/pgml-dashboard/src/api/cms.rs +++ b/pgml-dashboard/src/api/cms.rs @@ -5,6 +5,7 @@ use std::{ use std::str::FromStr; +use rocket::form::Form; use comrak::{format_html_with_plugins, parse_document, Arena, ComrakPlugins}; use lazy_static::lazy_static; use markdown::mdast::Node; @@ -646,9 +647,16 @@ impl Collection { } } + +#[post("/search_event", data = "")] +async fn search_event(search_event: Form, site_search: &State) -> ResponseOk { + eprintln!("WE GOT IT: {:?}", search_event.clicked); + ResponseOk("".to_string()) +} + #[get("/search?", rank = 20)] async fn search(query: &str, site_search: &State) -> ResponseOk { - let results = site_search + let (search_id, results) = site_search .search(query, None, None) .await .expect("Error performing search"); @@ -688,6 +696,7 @@ async fn search(query: &str, site_search: &State&", rank = 20)] async fn search_blog(query: &str, tag: &str, site_search: &State) -> ResponseOk { - let tag = if tag.len() > 0 { + let tag = if !tag.is_empty() { Some(Vec::from([tag.to_string()])) } else { None }; // If user is not making a search return all blogs in default design. - let results = if query.len() > 0 || tag.clone().is_some() { + let results = if !query.is_empty() || tag.clone().is_some() { let results = site_search.search(query, Some(DocType::Blog), tag.clone()).await; - let results = match results { - Ok(results) => results + match results { + Ok((_search_id, results)) => results .into_iter() - .map(|document| article_preview::DocMeta::from_document(document)) + .map(article_preview::DocMeta::from_document) .collect::>(), Err(_) => Vec::new(), - }; - - results + } } else { let mut results = Vec::new(); @@ -728,7 +735,7 @@ async fn search_blog(query: &str, tag: &str, site_search: &State 0 || tag.is_some(); + let is_search = !query.is_empty() || tag.is_some(); ResponseOk( crate::components::pages::blog::blog_search::Response::new() @@ -896,6 +903,7 @@ pub fn routes() -> Vec { get_docs_asset, get_user_guides, search, + search_event, search_blog ] } diff --git a/pgml-dashboard/src/forms.rs b/pgml-dashboard/src/forms.rs index 22f94f264..53ff66008 100644 --- a/pgml-dashboard/src/forms.rs +++ b/pgml-dashboard/src/forms.rs @@ -30,3 +30,9 @@ pub struct ChatbotPostData { #[serde(rename = "knowledgeBase")] pub knowledge_base: u8, } + +#[derive(FromForm)] +pub struct SearchEvent { + pub search_id: i64, + pub clicked: i64 +} diff --git a/pgml-dashboard/src/templates/docs.rs b/pgml-dashboard/src/templates/docs.rs index 36a101c07..67d7e77a1 100644 --- a/pgml-dashboard/src/templates/docs.rs +++ b/pgml-dashboard/src/templates/docs.rs @@ -8,6 +8,7 @@ use crate::utils::markdown::SearchResult; #[derive(TemplateOnce)] #[template(path = "components/search.html")] pub struct Search { + pub search_id: i64, pub query: String, pub results: Vec, } diff --git a/pgml-dashboard/src/utils/markdown.rs b/pgml-dashboard/src/utils/markdown.rs index 424dc81e0..f0f22fe12 100644 --- a/pgml-dashboard/src/utils/markdown.rs +++ b/pgml-dashboard/src/utils/markdown.rs @@ -1291,7 +1291,7 @@ impl SiteSearch { query: &str, doc_type: Option, doc_tags: Option>, - ) -> anyhow::Result> { + ) -> anyhow::Result<(i64, Vec)> { let mut search = serde_json::json!({ "query": { // "full_text_search": { @@ -1335,15 +1335,22 @@ impl SiteSearch { } let results = self.collection.search_local(search.into(), &self.pipeline).await?; - results["results"] - .as_array() - .context("Error getting results from search")? - .iter() - .map(|r| { - let document: Document = serde_json::from_value(r["document"].clone())?; - Ok(document) - }) - .collect() + let search_id = results["search_id"] + .as_i64() + .context("Error getting search_id from search")?; + + Ok(( + search_id, + results["results"] + .as_array() + .context("Error getting results from search")? + .iter() + .map(|r| { + let document: Document = serde_json::from_value(r["document"].clone())?; + anyhow::Ok(document) + }) + .collect::>>()?, + )) } pub async fn build(&mut self) -> anyhow::Result<()> { diff --git a/pgml-dashboard/static/js/search.js b/pgml-dashboard/static/js/search.js index 02bd989b9..37f5036f6 100644 --- a/pgml-dashboard/static/js/search.js +++ b/pgml-dashboard/static/js/search.js @@ -1,48 +1,67 @@ import { - Controller + Controller } from '@hotwired/stimulus' export default class extends Controller { - static targets = [ - 'searchTrigger', - ] - - connect() { - this.target = document.getElementById("search"); - this.searchInput = document.getElementById("search-input"); - this.searchFrame = document.getElementById("search-results") - - this.target.addEventListener('shown.bs.modal', this.focusSearchInput) - this.target.addEventListener('hidden.bs.modal', this.updateSearch) - this.searchInput.addEventListener('input', (e) => this.search(e)) - - this.timer; - } - - search(e) { - clearTimeout(this.timer); - const query = e.currentTarget.value - this.timer = setTimeout(() => { - this.searchFrame.src = `/search?query=${query}` - }, 250); - } - - focusSearchInput = (e) => { - this.searchInput.focus() - this.searchTriggerTarget.blur() - } - - updateSearch = () => { - this.searchTriggerTarget.value = this.searchInput.value - } - - openSearch = (e) => { - new bootstrap.Modal(this.target).show() - this.searchInput.value = e.currentTarget.value - } - - disconnect() { - this.searchTriggerTarget.removeEventListener('shown.bs.modal', this.focusSearchInput) - this.searchTriggerTarget.removeEventListener('hidden.bs.modal', this.updateSearch) - } + static targets = [ + 'searchTrigger', + ] + + connect() { + this.target = document.getElementById("search"); + this.searchInput = document.getElementById("search-input"); + this.searchFrame = document.getElementById("search-results") + + this.target.addEventListener('shown.bs.modal', this.focusSearchInput) + this.target.addEventListener('hidden.bs.modal', this.updateSearch) + this.searchInput.addEventListener('input', (e) => this.search(e)) + + this.timer; + + // Listen to click events and store clicked results + document.addEventListener("click", function(e) { + const target = e.target.closest(".search-result"); + if (target) { + const resultIndex = target.getAttribute("data-result-index"); + const searchId = target.getAttribute("data-search-id"); + fetch('/search_event', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + search_id: searchId, + clicked: resultIndex, + }), + }); + } + }); + } + + search(e) { + clearTimeout(this.timer); + const query = e.currentTarget.value + this.timer = setTimeout(() => { + this.searchFrame.src = `/search?query=${query}` + }, 250); + } + + focusSearchInput = (e) => { + this.searchInput.focus() + this.searchTriggerTarget.blur() + } + + updateSearch = () => { + this.searchTriggerTarget.value = this.searchInput.value + } + + openSearch = (e) => { + new bootstrap.Modal(this.target).show() + this.searchInput.value = e.currentTarget.value + } + + disconnect() { + this.searchTriggerTarget.removeEventListener('shown.bs.modal', this.focusSearchInput) + this.searchTriggerTarget.removeEventListener('hidden.bs.modal', this.updateSearch) + } } diff --git a/pgml-dashboard/templates/components/search.html b/pgml-dashboard/templates/components/search.html index 5fa45bd1e..4d795631e 100644 --- a/pgml-dashboard/templates/components/search.html +++ b/pgml-dashboard/templates/components/search.html @@ -1,11 +1,11 @@
- <% if query.len() < 1 { %> + <% if query.is_empty() { %>

Type to start searching

<% } else if !results.is_empty() { - for result in results.iter() { + for (i, result) in results.iter().enumerate() { %> - +
<%= result.title %>
<% if !result.snippet.is_empty() { %> From 6a343ceb8c91826220899372ebb0c1572c396473 Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:15:08 -0700 Subject: [PATCH 2/3] Working add search events --- pgml-dashboard/src/api/cms.rs | 9 +++++-- pgml-dashboard/src/utils/markdown.rs | 7 ++++++ pgml-dashboard/static/js/search.js | 37 ++++++++++++++-------------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/pgml-dashboard/src/api/cms.rs b/pgml-dashboard/src/api/cms.rs index 53964c29f..089ff6a26 100644 --- a/pgml-dashboard/src/api/cms.rs +++ b/pgml-dashboard/src/api/cms.rs @@ -650,8 +650,13 @@ impl Collection { #[post("/search_event", data = "")] async fn search_event(search_event: Form, site_search: &State) -> ResponseOk { - eprintln!("WE GOT IT: {:?}", search_event.clicked); - ResponseOk("".to_string()) + match site_search.add_search_event(search_event.search_id, search_event.clicked).await { + Ok(_) => ResponseOk("ok".to_string()), + Err(e) => { + eprintln!("{:?}", e); + ResponseOk("error".to_string()) + } + } } #[get("/search?", rank = 20)] diff --git a/pgml-dashboard/src/utils/markdown.rs b/pgml-dashboard/src/utils/markdown.rs index f0f22fe12..0aed062cc 100644 --- a/pgml-dashboard/src/utils/markdown.rs +++ b/pgml-dashboard/src/utils/markdown.rs @@ -1286,6 +1286,13 @@ impl SiteSearch { .collect() } + pub async fn add_search_event(&self, search_id: i64, search_result: i64) -> anyhow::Result<()> { + self.collection.add_search_event(search_id, search_result + 1, serde_json::json!({ + "clicked": true + }).into(), &self.pipeline).await?; + Ok(()) + } + pub async fn search( &self, query: &str, diff --git a/pgml-dashboard/static/js/search.js b/pgml-dashboard/static/js/search.js index 37f5036f6..1569790c0 100644 --- a/pgml-dashboard/static/js/search.js +++ b/pgml-dashboard/static/js/search.js @@ -18,24 +18,22 @@ export default class extends Controller { this.timer; - // Listen to click events and store clicked results - document.addEventListener("click", function(e) { - const target = e.target.closest(".search-result"); - if (target) { - const resultIndex = target.getAttribute("data-result-index"); - const searchId = target.getAttribute("data-search-id"); - fetch('/search_event', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - search_id: searchId, - clicked: resultIndex, - }), - }); - } - }); + document.addEventListener("click", this.handle_search_click); + } + + handle_search_click(e) { + const target = e.target.closest(".search-result"); + if (target) { + const resultIndex = target.getAttribute("data-result-index"); + const searchId = target.getAttribute("data-search-id"); + const formData = new FormData(); + formData.append("search_id", searchId); + formData.append("clicked", resultIndex); + fetch('/search_event', { + method: 'POST', + body: formData, + }); + } } search(e) { @@ -46,7 +44,7 @@ export default class extends Controller { }, 250); } - focusSearchInput = (e) => { + focusSearchInput = () => { this.searchInput.focus() this.searchTriggerTarget.blur() } @@ -63,5 +61,6 @@ export default class extends Controller { disconnect() { this.searchTriggerTarget.removeEventListener('shown.bs.modal', this.focusSearchInput) this.searchTriggerTarget.removeEventListener('hidden.bs.modal', this.updateSearch) + document.removeEventListener("click", this.handle_search_click); } } From 8918309f6c45d1a3bea56118481f1abad58f29c6 Mon Sep 17 00:00:00 2001 From: SilasMarvin <19626586+SilasMarvin@users.noreply.github.com> Date: Fri, 15 Mar 2024 15:06:27 -0700 Subject: [PATCH 3/3] Add search event tracking to the blogs page --- pgml-dashboard/src/api/cms.rs | 34 ++++++++------ .../cards/blog/article_preview/mod.rs | 8 +++- .../cards/blog/article_preview/template.html | 5 ++ .../blog/blog_search/call/call_controller.js | 21 +++++++++ .../pages/blog/blog_search/response/mod.rs | 46 +++++++++++++------ .../pages/blog/landing_page/template.html | 2 +- 6 files changed, 86 insertions(+), 30 deletions(-) diff --git a/pgml-dashboard/src/api/cms.rs b/pgml-dashboard/src/api/cms.rs index 089ff6a26..03adf87d7 100644 --- a/pgml-dashboard/src/api/cms.rs +++ b/pgml-dashboard/src/api/cms.rs @@ -5,10 +5,10 @@ use std::{ use std::str::FromStr; -use rocket::form::Form; use comrak::{format_html_with_plugins, parse_document, Arena, ComrakPlugins}; use lazy_static::lazy_static; use markdown::mdast::Node; +use rocket::form::Form; use rocket::{fs::NamedFile, http::uri::Origin, route::Route, State}; use yaml_rust::YamlLoader; @@ -647,14 +647,19 @@ impl Collection { } } - #[post("/search_event", data = "")] -async fn search_event(search_event: Form, site_search: &State) -> ResponseOk { - match site_search.add_search_event(search_event.search_id, search_event.clicked).await { +async fn search_event( + search_event: Form, + site_search: &State, +) -> ResponseOk { + match site_search + .add_search_event(search_event.search_id, search_event.clicked) + .await + { Ok(_) => ResponseOk("ok".to_string()), Err(e) => { eprintln!("{:?}", e); - ResponseOk("error".to_string()) + ResponseOk("error".to_string()) } } } @@ -718,15 +723,18 @@ async fn search_blog(query: &str, tag: &str, site_search: &State results - .into_iter() - .map(article_preview::DocMeta::from_document) - .collect::>(), - Err(_) => Vec::new(), + Ok((search_id, results)) => ( + Some(search_id), + results + .into_iter() + .map(article_preview::DocMeta::from_document) + .collect::>(), + ), + Err(_) => (None, Vec::new()), } } else { let mut results = Vec::new(); @@ -737,13 +745,13 @@ async fn search_blog(query: &str, tag: &str, site_search: &State, + search_result_index: Option, } impl ArticlePreview { - pub fn new(meta: &DocMeta) -> ArticlePreview { + pub fn new(meta: &DocMeta, search_id: Option, search_result_index: Option) -> ArticlePreview { ArticlePreview { card_type: String::from("default"), meta: meta.to_owned(), + search_id, + search_result_index, } } @@ -76,7 +80,7 @@ impl ArticlePreview { pub async fn from_path(path: &str) -> ArticlePreview { let doc = Document::from_path(&PathBuf::from(path)).await.unwrap(); let meta = DocMeta::from_document(doc); - ArticlePreview::new(&meta) + ArticlePreview::new(&meta, None, None) } } diff --git a/pgml-dashboard/src/components/cards/blog/article_preview/template.html b/pgml-dashboard/src/components/cards/blog/article_preview/template.html index 214479ec8..19a49bc0c 100644 --- a/pgml-dashboard/src/components/cards/blog/article_preview/template.html +++ b/pgml-dashboard/src/components/cards/blog/article_preview/template.html @@ -41,7 +41,12 @@

{}

); %> +<% +if let (Some(search_id), Some(search_result_index)) = (search_id, search_result_index) { %> +
+<% } else { %>
+<% } %> <% if card_type == String::from("featured") {%>
diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js b/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js index 79a4bd368..68fbaaacc 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js @@ -10,6 +10,23 @@ export default class extends Controller { connect() { this.timer; this.tags = ""; + + document.addEventListener("click", this.handle_search_click); + } + + handle_search_click(e) { + const target = e.target.closest(".blog-search-result"); + if (target) { + const resultIndex = target.getAttribute("data-result-index"); + const searchId = target.getAttribute("data-search-id"); + const formData = new FormData(); + formData.append("search_id", searchId); + formData.append("clicked", resultIndex); + fetch('/search_event', { + method: 'POST', + body: formData, + }); + } } search() { @@ -49,4 +66,8 @@ export default class extends Controller { this.tags = ""; this.search(); } + + disconnect() { + document.removeEventListener("click", this.handle_search_click); + } } diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs b/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs index ac8a89af1..5091ff85e 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs +++ b/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs @@ -6,11 +6,15 @@ use sailfish::TemplateOnce; #[template(path = "pages/blog/blog_search/response/template.html")] pub struct Response { html: Vec, + search_id: Option, } impl Response { - pub fn new() -> Response { - Response { html: Vec::new() } + pub fn new(search_id: Option) -> Response { + Response { + html: Vec::new(), + search_id, + } } pub fn pattern(mut self, mut articles: Vec, is_search: bool) -> Response { @@ -53,7 +57,8 @@ impl Response { }; articles.reverse(); - while articles.len() > 0 { + let mut search_result_index = 0; + while !articles.is_empty() { // Get the row pattern or repeat the last two row patterns. let pattern = match layout.get(cycle) { Some(pattern) => pattern, @@ -74,11 +79,12 @@ impl Response { for (i, doc) in row.into_iter().enumerate() { let template = pattern[i]; html.push( - ArticlePreview::new(&doc.unwrap()) + ArticlePreview::new(&doc.unwrap(), self.search_id, Some(search_result_index)) .card_type(template) .render_once() .unwrap(), - ) + ); + search_result_index += 1; } } else { html.push(format!( @@ -101,24 +107,36 @@ impl Response { {}
"#, - ArticlePreview::new(&row[0].clone().unwrap()) + ArticlePreview::new(&row[0].clone().unwrap(), self.search_id, Some(search_result_index)) .big() .render_once() .unwrap(), - ArticlePreview::new(&row[1].clone().unwrap()).render_once().unwrap(), - ArticlePreview::new(&row[2].clone().unwrap()).render_once().unwrap(), - ArticlePreview::new(&row[0].clone().unwrap()).render_once().unwrap(), - ArticlePreview::new(&row[1].clone().unwrap()).render_once().unwrap(), - ArticlePreview::new(&row[2].clone().unwrap()).render_once().unwrap() - )) + ArticlePreview::new(&row[1].clone().unwrap(), self.search_id, Some(search_result_index + 1)) + .render_once() + .unwrap(), + ArticlePreview::new(&row[2].clone().unwrap(), self.search_id, Some(search_result_index + 2)) + .render_once() + .unwrap(), + ArticlePreview::new(&row[0].clone().unwrap(), self.search_id, Some(search_result_index + 3)) + .render_once() + .unwrap(), + ArticlePreview::new(&row[1].clone().unwrap(), self.search_id, Some(search_result_index + 4)) + .render_once() + .unwrap(), + ArticlePreview::new(&row[2].clone().unwrap(), self.search_id, Some(search_result_index + 5)) + .render_once() + .unwrap() + )); + search_result_index += 6; } } else { html.push( - ArticlePreview::new(&articles.pop().unwrap()) + ArticlePreview::new(&articles.pop().unwrap(), self.search_id, Some(search_result_index)) .card_type("default") .render_once() .unwrap(), - ) + ); + search_result_index += 1; } cycle += 1; } diff --git a/pgml-dashboard/src/components/pages/blog/landing_page/template.html b/pgml-dashboard/src/components/pages/blog/landing_page/template.html index c52f1c628..af8cb9505 100644 --- a/pgml-dashboard/src/components/pages/blog/landing_page/template.html +++ b/pgml-dashboard/src/components/pages/blog/landing_page/template.html @@ -7,7 +7,7 @@ use crate::utils::config::standalone_dashboard; let cards = featured_cards.iter().map(|card| { - ArticlePreview::new(card).featured().render_once().unwrap() + ArticlePreview::new(card, None, None).featured().render_once().unwrap() }).collect::>(); %> 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