mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 16:09:05 +00:00
fix(overview): locked reason, one-line comments, stricter query parsing
This commit is contained in:
@@ -401,7 +401,7 @@ pub async fn org_summary(Path(org_id): Path<String>) -> impl IntoResponse {
|
||||
params(
|
||||
("org_id" = String, Path, description = "Organization name"),
|
||||
("top" = Option<u32>, Query, description = "Top streams per stream type by size. Default 5, max 20."),
|
||||
("refresh" = Option<bool>, Query, description = "Set true to skip the short-lived cache and rebuild now."),
|
||||
("refresh" = Option<bool>, Query, description = "Set true (or 1) to skip the short-lived cache and rebuild now."),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Success", content_type = "application/json", body = Object),
|
||||
@@ -418,17 +418,19 @@ pub async fn org_overview(
|
||||
Headers(user_email): Headers<UserEmail>,
|
||||
Query(query): Query<HashMap<String, String>>,
|
||||
) -> Response {
|
||||
let top = match query.get("top").map(|v| v.parse::<u32>()) {
|
||||
let top = match query.get("top").map(|v| v.parse::<usize>()) {
|
||||
Some(Err(_)) => {
|
||||
return MetaHttpResponse::bad_request("'top' must be an integer, clamped to 1..20");
|
||||
return MetaHttpResponse::bad_request(
|
||||
"'top' must be a non-negative integer; it is clamped to 1..20",
|
||||
);
|
||||
}
|
||||
Some(Ok(n)) => Some(n as usize),
|
||||
Some(Ok(n)) => Some(n),
|
||||
None => None,
|
||||
};
|
||||
let top_n = overview::normalize_top_n(top);
|
||||
let refresh = query
|
||||
.get("refresh")
|
||||
.is_some_and(|v| v == "true" || v == "1");
|
||||
.is_some_and(|v| v.eq_ignore_ascii_case("true") || v == "1");
|
||||
if !refresh && let Some(hit) = overview::cached_overview(&org_id, &user_email.user_id, top_n) {
|
||||
return Json(hit).into_response();
|
||||
}
|
||||
|
||||
+34
-19
@@ -39,8 +39,7 @@ use stream::try_get_streams;
|
||||
|
||||
const DEFAULT_TOP_N: usize = 5;
|
||||
const MAX_TOP_N: usize = 20;
|
||||
/// User-facing stream types only; profiles, service graph, filelist and internal streams are left
|
||||
/// out.
|
||||
/// User-facing stream types; derived and internal streams are left out.
|
||||
const OVERVIEW_STREAM_TYPES: [StreamType; 4] = [
|
||||
StreamType::Logs,
|
||||
StreamType::Metrics,
|
||||
@@ -97,6 +96,9 @@ pub struct Section<T> {
|
||||
pub data: Option<T>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
/// Why a section is locked, so the UI can pick the right message.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
impl<T> Section<T> {
|
||||
@@ -105,6 +107,7 @@ impl<T> Section<T> {
|
||||
status: SectionStatus::Ok,
|
||||
data: Some(data),
|
||||
error: None,
|
||||
reason: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +116,7 @@ impl<T> Section<T> {
|
||||
status: SectionStatus::Error,
|
||||
data: None,
|
||||
error: Some(message.to_string()),
|
||||
reason: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,14 +125,16 @@ impl<T> Section<T> {
|
||||
status: SectionStatus::Forbidden,
|
||||
data: None,
|
||||
error: None,
|
||||
reason: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn locked() -> Self {
|
||||
fn locked(reason: &str) -> Self {
|
||||
Self {
|
||||
status: SectionStatus::Locked,
|
||||
data: None,
|
||||
error: None,
|
||||
reason: Some(reason.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +202,7 @@ pub struct AlertsData {
|
||||
pub by_state: BTreeMap<String, u64>,
|
||||
/// Enabled rules by the severity level of their last classification.
|
||||
pub by_level: BTreeMap<String, u64>,
|
||||
/// Same count as `by_state.error`; do not add the two together.
|
||||
pub failed_evaluation: u64,
|
||||
}
|
||||
|
||||
@@ -242,14 +249,6 @@ pub async fn resolve_scope(org_id: &str, user_id: &str) -> Result<OverviewScope,
|
||||
})
|
||||
}
|
||||
|
||||
async fn list_grants(
|
||||
org_id: &str,
|
||||
user_id: &str,
|
||||
resource: &str,
|
||||
) -> Result<Option<Vec<String>>, anyhow::Error> {
|
||||
crate::authz::list_objects_for_user(org_id, user_id, "GET", &ofga_object_type(resource)).await
|
||||
}
|
||||
|
||||
/// Overview for a caller whose grants could not be resolved; each scoped section reports an error.
|
||||
pub async fn scope_unavailable() -> Overview {
|
||||
Overview {
|
||||
@@ -297,6 +296,14 @@ pub async fn get_overview(org_id: &str, scope: &OverviewScope, top_n: usize) ->
|
||||
overview
|
||||
}
|
||||
|
||||
async fn list_grants(
|
||||
org_id: &str,
|
||||
user_id: &str,
|
||||
resource: &str,
|
||||
) -> Result<Option<Vec<String>>, anyhow::Error> {
|
||||
crate::authz::list_objects_for_user(org_id, user_id, "GET", &ofga_object_type(resource)).await
|
||||
}
|
||||
|
||||
async fn build_overview(org_id: &str, scope: &OverviewScope, top_n: usize) -> Overview {
|
||||
let (streams, alerts, pipelines) = tokio::join!(
|
||||
async {
|
||||
@@ -494,7 +501,7 @@ async fn limits_section() -> Section<LimitsData> {
|
||||
};
|
||||
|
||||
if get_license().await.is_none() {
|
||||
return Section::locked();
|
||||
return Section::locked("no license loaded");
|
||||
}
|
||||
Section::ok(LimitsData {
|
||||
scope: "deployment",
|
||||
@@ -506,7 +513,7 @@ async fn limits_section() -> Section<LimitsData> {
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
async fn limits_section() -> Section<LimitsData> {
|
||||
Section::locked()
|
||||
Section::locked("not available in this edition")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -634,8 +641,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_section_serialization_carries_status_and_omits_empty_fields() {
|
||||
let locked = serde_json::to_value(Section::<PipelinesData>::locked()).unwrap();
|
||||
assert_eq!(locked, serde_json::json!({"status": "locked"}));
|
||||
let locked = serde_json::to_value(Section::<PipelinesData>::locked("no license")).unwrap();
|
||||
assert_eq!(
|
||||
locked,
|
||||
serde_json::json!({"status": "locked", "reason": "no license"})
|
||||
);
|
||||
let err = serde_json::to_value(Section::<PipelinesData>::error("boom")).unwrap();
|
||||
assert_eq!(err, serde_json::json!({"status": "error", "error": "boom"}));
|
||||
}
|
||||
@@ -643,7 +653,12 @@ mod tests {
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
#[tokio::test]
|
||||
async fn test_limits_locked_without_enterprise() {
|
||||
assert_eq!(limits_section().await.status, SectionStatus::Locked);
|
||||
let limits = limits_section().await;
|
||||
assert_eq!(limits.status, SectionStatus::Locked);
|
||||
assert_eq!(
|
||||
limits.reason.as_deref(),
|
||||
Some("not available in this edition")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -756,9 +771,9 @@ mod tests {
|
||||
vec![stream("only_for_admin", StreamType::Logs, 7.0, 1)],
|
||||
5,
|
||||
)),
|
||||
alerts: Section::locked(),
|
||||
pipelines: Section::locked(),
|
||||
limits: Section::locked(),
|
||||
alerts: Section::locked("test"),
|
||||
pipelines: Section::locked("test"),
|
||||
limits: Section::locked("test"),
|
||||
generated_at: now_micros(),
|
||||
cached: false,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user