fix(meilisearch): show per-index size in the index sidebar

This commit is contained in:
zipg authored and GitHub committed 2026-09-21 18:34:28 +08:00
1 parent c30f6821d3
commit be055a60cc
14 files changed
+239 -18

No files matched your search

+86 -16
View File
@@ -1093,10 +1093,39 @@ pub struct MeilisearchIndexOverview {
pub updated_at: Option<String>,
pub number_of_documents: u64,
pub is_indexing: bool,
/// Raw document store size of *this* index in bytes (`rawDocumentDbSize`,
/// Meilisearch >= 1.14). `None` on servers that do not report per-index sizes.
pub document_size: Option<u64>,
/// Average size of one document in this index in bytes (`avgDocumentSize`,
/// Meilisearch >= 1.14). `None` on older servers.
pub avg_document_size: Option<u64>,
/// Instance-wide database size in bytes; `None` when the API key cannot read instance stats.
/// It counts every index of the instance, so the UI only uses it as a fallback.
pub database_size: Option<u64>,
}
/// Builds the index overview from the three responses the tab needs. Kept pure
/// so the per-index size fields (Meilisearch >= 1.14) and the older-server
/// fallback can be covered without a live server.
fn parse_index_overview(
index: &str,
info: &Value,
stats: &Value,
instance_stats: Option<&Value>,
) -> MeilisearchIndexOverview {
MeilisearchIndexOverview {
uid: info.get("uid").and_then(Value::as_str).unwrap_or(index).to_string(),
primary_key: info.get("primaryKey").and_then(Value::as_str).map(str::to_string),
created_at: info.get("createdAt").and_then(Value::as_str).map(str::to_string),
updated_at: info.get("updatedAt").and_then(Value::as_str).map(str::to_string),
number_of_documents: stats.get("numberOfDocuments").and_then(Value::as_u64).unwrap_or(0),
is_indexing: stats.get("isIndexing").and_then(Value::as_bool).unwrap_or(false),
document_size: stats.get("rawDocumentDbSize").and_then(Value::as_u64),
avg_document_size: stats.get("avgDocumentSize").and_then(Value::as_u64),
database_size: instance_stats.and_then(|value| value.get("databaseSize")).and_then(Value::as_u64),
}
}
pub async fn get_index_overview(client: &MeilisearchClient, index: &str) -> Result<MeilisearchIndexOverview, String> {
let info = response_json(
client
@@ -1109,22 +1138,11 @@ pub async fn get_index_overview(client: &MeilisearchClient, index: &str) -> Resu
.await?;
let stats = get_index_stats(client, index).await?;
// Instance stats require broader key permissions, so keep them best-effort.
let database_size = match client.get("/stats").send().await {
Ok(response) => response_json(response, "instance stats lookup")
.await
.ok()
.and_then(|value| value.get("databaseSize").and_then(Value::as_u64)),
let instance_stats = match client.get("/stats").send().await {
Ok(response) => response_json(response, "instance stats lookup").await.ok(),
Err(_) => None,
};
Ok(MeilisearchIndexOverview {
uid: info.get("uid").and_then(Value::as_str).unwrap_or(index).to_string(),
primary_key: info.get("primaryKey").and_then(Value::as_str).map(str::to_string),
created_at: info.get("createdAt").and_then(Value::as_str).map(str::to_string),
updated_at: info.get("updatedAt").and_then(Value::as_str).map(str::to_string),
number_of_documents: stats.get("numberOfDocuments").and_then(Value::as_u64).unwrap_or(0),
is_indexing: stats.get("isIndexing").and_then(Value::as_bool).unwrap_or(false),
database_size,
})
Ok(parse_index_overview(index, &info, &stats, instance_stats.as_ref()))
}
pub async fn delete_index(client: &MeilisearchClient, index: &str) -> Result<(), String> {
@@ -1904,8 +1922,9 @@ mod tests {
use super::{
decode_bounded_rest_body, decoded_identity, ensure_task_index, filter_expression, identity_path,
meilisearch_base_url, meilisearch_filter_from_request, meilisearch_search_body, meilisearch_sort_from_request,
parse_key, parse_meilisearch_search_response, parse_rest_request_line, parse_system_stats, task_selector_query,
MeilisearchClient, MeilisearchOverviewSection, MeilisearchTask, MeilisearchTaskSelector,
parse_index_overview, parse_key, parse_meilisearch_search_response, parse_rest_request_line,
parse_system_stats, task_selector_query, MeilisearchClient, MeilisearchOverviewSection, MeilisearchTask,
MeilisearchTaskSelector,
};
use reqwest::Method;
use serde_json::{json, Value};
@@ -2037,6 +2056,57 @@ mod tests {
assert!(indexless.contains("not associated with an index"));
}
#[test]
fn index_overview_prefers_the_per_index_size_when_the_server_reports_one() {
// Meilisearch >= 1.14: /indexes/{uid}/stats carries the index's own sizes.
let overview = parse_index_overview(
"movies",
&json!({ "uid": "movies", "primaryKey": "id", "createdAt": "2026-08-01T00:00:00Z" }),
&json!({
"numberOfDocuments": 256,
"indexSize": 90_000_000,
"rawDocumentDbSize": 26_935_296,
"avgDocumentSize": 105_216,
"isIndexing": false
}),
Some(&json!({ "databaseSize": 999_999_999 })),
);
assert_eq!(overview.document_size, Some(26_935_296));
assert_eq!(overview.avg_document_size, Some(105_216));
// The instance-wide number stays available, but the UI must not present it as this index's size.
assert_eq!(overview.database_size, Some(999_999_999));
assert_eq!(overview.number_of_documents, 256);
}
#[test]
fn index_overview_leaves_per_index_sizes_unset_on_older_servers() {
// Meilisearch <= 1.13 answers index stats without rawDocumentDbSize / avgDocumentSize.
let overview = parse_index_overview(
"movies",
&json!({ "uid": "movies" }),
&json!({ "numberOfDocuments": 12, "isIndexing": true, "fieldDistribution": { "id": 12 } }),
Some(&json!({ "databaseSize": 4096 })),
);
assert_eq!(overview.document_size, None);
assert_eq!(overview.avg_document_size, None);
assert_eq!(overview.database_size, Some(4096));
assert_eq!(overview.number_of_documents, 12);
assert!(overview.is_indexing);
}
#[test]
fn index_overview_survives_a_forbidden_instance_stats_lookup() {
let overview = parse_index_overview("movies", &json!({}), &json!({ "numberOfDocuments": 1 }), None);
assert_eq!(overview.database_size, None);
assert_eq!(overview.document_size, None);
assert_eq!(overview.uid, "movies");
assert_eq!(overview.number_of_documents, 1);
assert!(!overview.is_indexing);
}
#[test]
fn system_stats_compute_totals_and_top_ten_deterministically() {
let indexes = (0..12)