From 1f0d319c675ba26ffc435061bf056356af06ae8e Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 21 Jul 2025 15:12:46 +0200 Subject: [PATCH] Add analytics --- .../meilisearch/src/routes/indexes/render.rs | 25 +++-- .../src/routes/indexes/render_analytics.rs | 101 ++++++++++++++++++ 2 files changed, 115 insertions(+), 11 deletions(-) diff --git a/crates/meilisearch/src/routes/indexes/render.rs b/crates/meilisearch/src/routes/indexes/render.rs index 85a6fce97..ca841ba0e 100644 --- a/crates/meilisearch/src/routes/indexes/render.rs +++ b/crates/meilisearch/src/routes/indexes/render.rs @@ -28,6 +28,7 @@ use crate::analytics::Analytics; use crate::extractors::authentication::policies::DoubleActionPolicy; use crate::extractors::authentication::GuardedData; use crate::extractors::sequential_extractor::SeqHandler; +use crate::routes::indexes::render_analytics::RenderAggregator; #[derive(OpenApi)] #[openapi( @@ -92,14 +93,16 @@ pub async fn render_post( let query = params.into_inner(); debug!(parameters = ?query, "Render template"); - //let mut aggregate = SimilarAggregator::::from_query(&query); + let mut aggregate = RenderAggregator::from_query(&query); - let result = render(index, query).await?; + let result = render(index, query).await; - // if let Ok(similar) = &similar { - // aggregate.succeed(similar); - // } - // analytics.publish(aggregate, &req); + if result.is_ok() { + aggregate.succeed(); + } + analytics.publish(aggregate, &req); + + let result = result?; debug!(returns = ?result, "Render template"); Ok(HttpResponse::Ok().json(result)) @@ -551,20 +554,20 @@ pub struct RenderQuery { #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] pub struct RenderQueryTemplate { #[deserr(default, error = DeserrJsonError)] - id: Option, + pub id: Option, #[deserr(default, error = DeserrJsonError)] - inline: Option, + pub inline: Option, } #[derive(Debug, Clone, Default, PartialEq, Deserr, ToSchema)] #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] pub struct RenderQueryInput { #[deserr(default, error = DeserrJsonError)] - document_id: Option, + pub document_id: Option, #[deserr(default, error = DeserrJsonError)] - insert_fields: Option, + pub insert_fields: Option, #[deserr(default, error = DeserrJsonError)] - inline: Option>, + pub inline: Option>, } #[derive(Debug, Clone, Serialize, PartialEq, ToSchema)] diff --git a/crates/meilisearch/src/routes/indexes/render_analytics.rs b/crates/meilisearch/src/routes/indexes/render_analytics.rs index 8b1378917..850a4aca5 100644 --- a/crates/meilisearch/src/routes/indexes/render_analytics.rs +++ b/crates/meilisearch/src/routes/indexes/render_analytics.rs @@ -1 +1,102 @@ +use serde_json::json; +use crate::analytics::Aggregate; +use crate::routes::indexes::render::RenderQuery; + +#[derive(Default)] +pub struct RenderAggregator { + // requests + total_received: usize, + total_succeeded: usize, + + // parameters + template_inline: bool, + template_id: bool, + input_inline: bool, + input_id: bool, + input_omitted: bool, + fields_forced: bool, + fields_disabled: bool, +} + +impl RenderAggregator { + #[allow(clippy::field_reassign_with_default)] + pub fn from_query(query: &RenderQuery) -> Self { + let RenderQuery { + template, + input, + } = query; + + let mut ret = Self::default(); + + ret.total_received = 1; + + ret.template_inline = template.inline.is_some(); + ret.template_id = template.id.is_some(); + ret.input_inline = input.as_ref().is_some_and(|i| i.inline.is_some()); + ret.input_id = input.as_ref().is_some_and(|i| i.document_id.is_some()); + ret.input_omitted = input.as_ref().is_none(); + ret.fields_forced = input.as_ref().is_some_and(|i| i.insert_fields.is_some()); + ret.fields_disabled = input.as_ref().is_some_and(|i| i.insert_fields.is_none()); + + ret + } + + pub fn succeed(&mut self) { + self.total_succeeded += 1; + } +} + +impl Aggregate for RenderAggregator { + fn event_name(&self) -> &'static str { + "Documents Rendered" + } + + fn aggregate(mut self: Box, new: Box) -> Box { + self.total_received += new.total_received; + self.total_succeeded += new.total_succeeded; + + self.template_inline |= new.template_inline; + self.template_id |= new.template_id; + self.input_inline |= new.input_inline; + self.input_id |= new.input_id; + self.input_omitted |= new.input_omitted; + self.fields_forced |= new.fields_forced; + self.fields_disabled |= new.fields_disabled; + + self + } + + fn into_event(self: Box) -> serde_json::Value { + let Self { + total_received, + total_succeeded, + template_inline, + template_id, + input_inline, + input_id, + input_omitted, + fields_forced, + fields_disabled, + } = *self; + + json!({ + "requests": { + "total_received": total_received, + "total_succeeded": total_succeeded, + "total_failed": total_received.saturating_sub(total_succeeded) // just to be sure we never panics + }, + "template": { + "inline": template_inline, + "id": template_id, + }, + "input": { + "inline": input_inline, + "id": input_id, + "omitted": input_omitted, + "fields_forced": fields_forced, + "fields_disabled": fields_disabled, + }, + }) + } +}