2024-10-17 01:04:25 +02:00
|
|
|
#![allow(clippy::transmute_ptr_to_ref)] // mopify isn't updated with the latest version of clippy yet
|
|
|
|
|
2024-10-16 15:43:27 +02:00
|
|
|
pub mod segment_analytics;
|
2021-10-27 18:16:13 +02:00
|
|
|
|
|
|
|
use std::fs;
|
|
|
|
use std::path::{Path, PathBuf};
|
2022-10-13 15:02:59 +02:00
|
|
|
use std::str::FromStr;
|
2021-10-27 18:16:13 +02:00
|
|
|
|
|
|
|
use actix_web::HttpRequest;
|
2022-10-13 15:02:59 +02:00
|
|
|
use meilisearch_types::InstanceUid;
|
2024-10-17 00:38:18 +02:00
|
|
|
use mopa::mopafy;
|
2021-10-27 18:16:13 +02:00
|
|
|
use once_cell::sync::Lazy;
|
|
|
|
use platform_dirs::AppDirs;
|
|
|
|
|
2023-09-21 17:01:05 +02:00
|
|
|
// if the feature analytics is enabled we use the real analytics
|
2021-10-27 18:16:13 +02:00
|
|
|
pub type SegmentAnalytics = segment_analytics::SegmentAnalytics;
|
2024-10-16 15:43:27 +02:00
|
|
|
pub use segment_analytics::SearchAggregator;
|
2024-10-16 17:16:33 +02:00
|
|
|
pub use segment_analytics::SimilarAggregator;
|
|
|
|
|
|
|
|
use self::segment_analytics::extract_user_agents;
|
2023-02-20 09:21:52 +01:00
|
|
|
pub type MultiSearchAggregator = segment_analytics::MultiSearchAggregator;
|
2023-04-26 17:08:55 +02:00
|
|
|
pub type FacetSearchAggregator = segment_analytics::FacetSearchAggregator;
|
2021-10-27 18:16:13 +02:00
|
|
|
|
2024-10-16 15:43:27 +02:00
|
|
|
/// A macro used to quickly define events that don't aggregate or send anything besides an empty event with its name.
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! empty_analytics {
|
|
|
|
($struct_name:ident, $event_name:literal) => {
|
|
|
|
#[derive(Default)]
|
|
|
|
struct $struct_name {}
|
|
|
|
|
|
|
|
impl $crate::analytics::Aggregate for $struct_name {
|
|
|
|
fn event_name(&self) -> &'static str {
|
|
|
|
$event_name
|
|
|
|
}
|
|
|
|
|
2024-10-17 00:38:18 +02:00
|
|
|
fn aggregate(self: Box<Self>, _other: Box<Self>) -> Box<Self> {
|
2024-10-16 15:43:27 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-10-17 00:38:18 +02:00
|
|
|
fn into_event(self: Box<Self>) -> serde_json::Value {
|
2024-10-16 15:43:27 +02:00
|
|
|
serde_json::json!({})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-01-26 17:43:16 +01:00
|
|
|
/// The Meilisearch config dir:
|
|
|
|
/// `~/.config/Meilisearch` on *NIX or *BSD.
|
2021-10-27 18:16:13 +02:00
|
|
|
/// `~/Library/ApplicationSupport` on macOS.
|
|
|
|
/// `%APPDATA` (= `C:\Users%USERNAME%\AppData\Roaming`) on windows.
|
|
|
|
static MEILISEARCH_CONFIG_PATH: Lazy<Option<PathBuf>> =
|
2022-01-26 17:43:16 +01:00
|
|
|
Lazy::new(|| AppDirs::new(Some("Meilisearch"), false).map(|appdir| appdir.config_dir));
|
2021-10-27 18:16:13 +02:00
|
|
|
|
|
|
|
fn config_user_id_path(db_path: &Path) -> Option<PathBuf> {
|
|
|
|
db_path
|
|
|
|
.canonicalize()
|
|
|
|
.ok()
|
2022-10-20 18:00:07 +02:00
|
|
|
.map(|path| path.join("instance-uid").display().to_string().replace('/', "-"))
|
2021-10-27 18:16:13 +02:00
|
|
|
.zip(MEILISEARCH_CONFIG_PATH.as_ref())
|
|
|
|
.map(|(filename, config_path)| config_path.join(filename.trim_start_matches('-')))
|
|
|
|
}
|
|
|
|
|
2022-01-26 17:43:16 +01:00
|
|
|
/// Look for the instance-uid in the `data.ms` or in `~/.config/Meilisearch/path-to-db-instance-uid`
|
2022-10-13 15:02:59 +02:00
|
|
|
fn find_user_id(db_path: &Path) -> Option<InstanceUid> {
|
2021-10-27 18:16:13 +02:00
|
|
|
fs::read_to_string(db_path.join("instance-uid"))
|
|
|
|
.ok()
|
2022-12-19 14:12:26 +01:00
|
|
|
.or_else(|| fs::read_to_string(config_user_id_path(db_path)?).ok())
|
2022-10-13 15:02:59 +02:00
|
|
|
.and_then(|uid| InstanceUid::from_str(&uid).ok())
|
2021-10-27 18:16:13 +02:00
|
|
|
}
|
|
|
|
|
2022-11-28 16:27:41 +01:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub enum DocumentDeletionKind {
|
|
|
|
PerDocumentId,
|
|
|
|
ClearAll,
|
|
|
|
PerBatch,
|
2023-05-02 17:46:04 +02:00
|
|
|
PerFilter,
|
2022-11-28 16:27:41 +01:00
|
|
|
}
|
|
|
|
|
2023-05-09 19:52:11 +02:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub enum DocumentFetchKind {
|
2024-06-06 11:29:16 +02:00
|
|
|
PerDocumentId { retrieve_vectors: bool },
|
|
|
|
Normal { with_filter: bool, limit: usize, offset: usize, retrieve_vectors: bool },
|
2023-05-09 19:52:11 +02:00
|
|
|
}
|
|
|
|
|
2024-10-17 00:38:18 +02:00
|
|
|
pub trait Aggregate: 'static + mopa::Any + Send {
|
2024-10-16 15:43:27 +02:00
|
|
|
fn event_name(&self) -> &'static str;
|
|
|
|
|
2024-10-17 00:38:18 +02:00
|
|
|
fn aggregate(self: Box<Self>, other: Box<Self>) -> Box<Self>
|
2024-10-16 15:43:27 +02:00
|
|
|
where
|
|
|
|
Self: Sized;
|
|
|
|
|
2024-10-17 00:38:18 +02:00
|
|
|
fn downcast_aggregate(
|
|
|
|
this: Box<dyn Aggregate>,
|
|
|
|
other: Box<dyn Aggregate>,
|
|
|
|
) -> Option<Box<dyn Aggregate>>
|
2024-10-16 15:43:27 +02:00
|
|
|
where
|
2024-10-17 00:38:18 +02:00
|
|
|
Self: Sized,
|
|
|
|
{
|
|
|
|
if this.is::<Self>() && other.is::<Self>() {
|
|
|
|
let this = this.downcast::<Self>().ok()?;
|
|
|
|
let other = other.downcast::<Self>().ok()?;
|
|
|
|
Some(Self::aggregate(this, other))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn into_event(self: Box<Self>) -> serde_json::Value;
|
2024-10-16 15:43:27 +02:00
|
|
|
}
|
|
|
|
|
2024-10-17 00:38:18 +02:00
|
|
|
mopafy!(Aggregate);
|
|
|
|
|
2024-10-16 15:43:27 +02:00
|
|
|
/// Helper trait to define multiple aggregate with the same content but a different name.
|
|
|
|
/// Commonly used when you must aggregate a search with POST or with GET for example.
|
2024-10-17 00:43:34 +02:00
|
|
|
pub trait AggregateMethod: 'static + Default + Send {
|
2024-10-16 15:43:27 +02:00
|
|
|
fn event_name() -> &'static str;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A macro used to quickly define multiple aggregate method with their name
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! aggregate_methods {
|
|
|
|
($method:ident => $event_name:literal) => {
|
2024-10-16 21:17:06 +02:00
|
|
|
#[derive(Default)]
|
|
|
|
pub struct $method {}
|
2024-10-16 15:43:27 +02:00
|
|
|
|
|
|
|
impl $crate::analytics::AggregateMethod for $method {
|
|
|
|
fn event_name() -> &'static str {
|
|
|
|
$event_name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
($($method:ident => $event_name:literal,)+) => {
|
|
|
|
$(
|
|
|
|
aggregate_methods!($method => $event_name);
|
|
|
|
)+
|
|
|
|
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Analytics {
|
2024-10-16 21:17:06 +02:00
|
|
|
segment: Option<SegmentAnalytics>,
|
2024-10-16 15:43:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Analytics {
|
|
|
|
fn no_analytics() -> Self {
|
2024-10-16 21:17:06 +02:00
|
|
|
Self { segment: None }
|
2024-10-16 15:43:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn segment_analytics(segment: SegmentAnalytics) -> Self {
|
2024-10-16 21:17:06 +02:00
|
|
|
Self { segment: Some(segment) }
|
2024-10-16 15:43:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn instance_uid(&self) -> Option<&InstanceUid> {
|
2024-10-16 21:17:06 +02:00
|
|
|
self.segment.as_ref().map(|segment| segment.instance_uid.as_ref())
|
2024-10-16 15:43:27 +02:00
|
|
|
}
|
2022-10-13 15:02:59 +02:00
|
|
|
|
2021-10-27 18:16:13 +02:00
|
|
|
/// The method used to publish most analytics that do not need to be batched every hours
|
2024-10-17 00:38:18 +02:00
|
|
|
pub fn publish<T: Aggregate>(&self, event: T, request: &HttpRequest) {
|
2024-10-16 21:17:06 +02:00
|
|
|
let Some(ref segment) = self.segment else { return };
|
2024-10-16 17:16:33 +02:00
|
|
|
let user_agents = extract_user_agents(request);
|
2024-10-17 00:38:18 +02:00
|
|
|
let _ = segment.sender.try_send(segment_analytics::Message::new(event));
|
2024-10-16 15:43:27 +02:00
|
|
|
}
|
2021-10-27 18:16:13 +02:00
|
|
|
}
|