mirror of https://github.com/buster-so/buster.git
get dashboards still need to tweak the metrics dashboard rel
This commit is contained in:
parent
7ab35b7f22
commit
561c31965e
|
@ -18,6 +18,7 @@ tracing = "0.1.40"
|
||||||
uuid = { version = "1.8", features = ["serde", "v4"] }
|
uuid = { version = "1.8", features = ["serde", "v4"] }
|
||||||
diesel = { version = "2", features = ["uuid", "chrono", "serde_json", "postgres"] }
|
diesel = { version = "2", features = ["uuid", "chrono", "serde_json", "postgres"] }
|
||||||
diesel-async = { version = "0.5.2", features = ["postgres", "bb8"] }
|
diesel-async = { version = "0.5.2", features = ["postgres", "bb8"] }
|
||||||
|
futures = "0.3.30"
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "bi_api"
|
name = "bi_api"
|
||||||
|
|
|
@ -15,6 +15,7 @@ tracing = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
diesel = { workspace = true }
|
diesel = { workspace = true }
|
||||||
diesel-async = { workspace = true }
|
diesel-async = { workspace = true }
|
||||||
|
futures = { workspace = true }
|
||||||
|
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
database = { path = "../database" }
|
database = { path = "../database" }
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable};
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use futures::future::{try_join_all, join_all};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde_yaml;
|
||||||
|
|
||||||
|
use crate::files::dashboard_files::types::{
|
||||||
|
BusterDashboard, BusterDashboardResponse, DashboardConfig, DashboardRow, DashboardRowItem,
|
||||||
|
};
|
||||||
|
use crate::files::metric_files::helpers::get_metric;
|
||||||
|
use database::enums::{AssetPermissionRole, Verification};
|
||||||
|
use database::pool::get_pg_pool;
|
||||||
|
use database::schema::dashboard_files;
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable)]
|
||||||
|
#[diesel(table_name = dashboard_files)]
|
||||||
|
struct QueryableDashboardFile {
|
||||||
|
id: Uuid,
|
||||||
|
name: String,
|
||||||
|
file_name: String,
|
||||||
|
content: Value,
|
||||||
|
filter: Option<String>,
|
||||||
|
organization_id: Uuid,
|
||||||
|
created_by: Uuid,
|
||||||
|
created_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
updated_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_dashboard(dashboard_id: &Uuid, user_id: &Uuid) -> Result<BusterDashboardResponse> {
|
||||||
|
let mut conn = match get_pg_pool().get().await {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(e) => return Err(anyhow!("Failed to get database connection: {}", e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Query the dashboard file
|
||||||
|
let dashboard_file = dashboard_files::table
|
||||||
|
.filter(dashboard_files::id.eq(dashboard_id))
|
||||||
|
.filter(dashboard_files::deleted_at.is_null())
|
||||||
|
.select((
|
||||||
|
dashboard_files::id,
|
||||||
|
dashboard_files::name,
|
||||||
|
dashboard_files::file_name,
|
||||||
|
dashboard_files::content,
|
||||||
|
dashboard_files::filter,
|
||||||
|
dashboard_files::organization_id,
|
||||||
|
dashboard_files::created_by,
|
||||||
|
dashboard_files::created_at,
|
||||||
|
dashboard_files::updated_at,
|
||||||
|
))
|
||||||
|
.first::<QueryableDashboardFile>(&mut conn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| match e {
|
||||||
|
diesel::result::Error::NotFound => anyhow!("Dashboard file not found"),
|
||||||
|
_ => anyhow!("Database error: {}", e),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Parse the content to get metric IDs and other dashboard info
|
||||||
|
let content = dashboard_file.content.clone();
|
||||||
|
let config = parse_dashboard_config(&content)?;
|
||||||
|
|
||||||
|
// Get updated_at from content if available, otherwise use the database value
|
||||||
|
let updated_at = content
|
||||||
|
.get("updated_at")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.and_then(|s| DateTime::parse_from_rfc3339(s).ok())
|
||||||
|
.map(|dt| dt.with_timezone(&Utc))
|
||||||
|
.unwrap_or(dashboard_file.updated_at);
|
||||||
|
|
||||||
|
// Get name from content if available, otherwise use the database value
|
||||||
|
let name = content
|
||||||
|
.get("name")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.unwrap_or(&dashboard_file.name)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Collect all metric IDs from the rows
|
||||||
|
let metric_ids: Vec<Uuid> = config
|
||||||
|
.rows
|
||||||
|
.iter()
|
||||||
|
.flat_map(|row| {
|
||||||
|
row.items.iter().filter_map(|item| {
|
||||||
|
Uuid::parse_str(&item.id).ok()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Fetch all metrics concurrently
|
||||||
|
let metric_futures: Vec<_> = metric_ids
|
||||||
|
.iter()
|
||||||
|
.map(|metric_id| get_metric(metric_id, user_id))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let metric_results = join_all(metric_futures).await;
|
||||||
|
let metrics: Vec<_> = metric_results
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|result| result.ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Construct the dashboard using content values where available
|
||||||
|
let dashboard = BusterDashboard {
|
||||||
|
config,
|
||||||
|
created_at: dashboard_file.created_at.to_string(),
|
||||||
|
created_by: dashboard_file.created_by.to_string(),
|
||||||
|
deleted_at: None,
|
||||||
|
description: content.get("description").and_then(|v| v.as_str().map(String::from)),
|
||||||
|
id: content
|
||||||
|
.get("id")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.unwrap_or(&dashboard_file.id.to_string())
|
||||||
|
.to_string(),
|
||||||
|
name,
|
||||||
|
updated_at: Some(updated_at.to_string()),
|
||||||
|
updated_by: dashboard_file.created_by.to_string(),
|
||||||
|
status: Verification::Verified,
|
||||||
|
version_number: content
|
||||||
|
.get("version_number")
|
||||||
|
.and_then(Value::as_i64)
|
||||||
|
.unwrap_or(1) as i32,
|
||||||
|
file: serde_yaml::to_string(&dashboard_file.content)?,
|
||||||
|
file_name: dashboard_file.file_name,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(BusterDashboardResponse {
|
||||||
|
access: AssetPermissionRole::Owner,
|
||||||
|
metrics,
|
||||||
|
dashboard,
|
||||||
|
permission: AssetPermissionRole::Owner,
|
||||||
|
public_password: None,
|
||||||
|
collections: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_dashboard_config(content: &Value) -> Result<DashboardConfig> {
|
||||||
|
let rows = content
|
||||||
|
.get("rows")
|
||||||
|
.ok_or_else(|| anyhow!("Missing rows in dashboard content"))?
|
||||||
|
.as_array()
|
||||||
|
.ok_or_else(|| anyhow!("Rows is not an array"))?
|
||||||
|
.iter()
|
||||||
|
.map(|row| {
|
||||||
|
let items = row
|
||||||
|
.get("items")
|
||||||
|
.ok_or_else(|| anyhow!("Missing items in row"))?
|
||||||
|
.as_array()
|
||||||
|
.ok_or_else(|| anyhow!("Items is not an array"))?
|
||||||
|
.iter()
|
||||||
|
.map(|item| {
|
||||||
|
Ok(DashboardRowItem {
|
||||||
|
id: item
|
||||||
|
.get("id")
|
||||||
|
.ok_or_else(|| anyhow!("Missing id in item"))?
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| anyhow!("Id is not a string"))?
|
||||||
|
.to_string(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
Ok(DashboardRow {
|
||||||
|
items,
|
||||||
|
row_height: None,
|
||||||
|
column_sizes: None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
Ok(DashboardConfig { rows })
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod get_dashboard;
|
||||||
|
|
||||||
|
pub use get_dashboard::*;
|
|
@ -0,0 +1,5 @@
|
||||||
|
mod types;
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
|
pub use types::*;
|
||||||
|
pub use helpers::*;
|
|
@ -0,0 +1,76 @@
|
||||||
|
use database::enums::{AssetPermissionRole, Verification};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::files::BusterMetric;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct BusterDashboardListItem {
|
||||||
|
pub created_at: String,
|
||||||
|
pub id: String,
|
||||||
|
pub last_edited: String,
|
||||||
|
pub members: Vec<DashboardMember>,
|
||||||
|
pub name: String,
|
||||||
|
pub owner: DashboardMember,
|
||||||
|
pub status: Verification,
|
||||||
|
pub is_shared: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct DashboardMember {
|
||||||
|
pub avatar_url: Option<String>,
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This extends BusterShare which needs to be defined
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct BusterDashboardResponse {
|
||||||
|
pub access: AssetPermissionRole,
|
||||||
|
pub metrics: Vec<BusterMetric>,
|
||||||
|
pub dashboard: BusterDashboard,
|
||||||
|
pub permission: AssetPermissionRole,
|
||||||
|
pub public_password: Option<String>,
|
||||||
|
pub collections: Vec<Collection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This extends BusterShare but omits certain fields
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct BusterDashboard {
|
||||||
|
pub config: DashboardConfig,
|
||||||
|
pub created_at: String,
|
||||||
|
pub created_by: String,
|
||||||
|
pub deleted_at: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub updated_at: Option<String>,
|
||||||
|
pub updated_by: String,
|
||||||
|
pub status: Verification,
|
||||||
|
pub version_number: i32,
|
||||||
|
pub file: String, // yaml file
|
||||||
|
pub file_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Collection {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This is a placeholder for DashboardConfig which needs to be defined based on your specific needs
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct DashboardConfig {
|
||||||
|
pub rows: Vec<DashboardRow>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct DashboardRow {
|
||||||
|
pub items: Vec<DashboardRowItem>,
|
||||||
|
pub row_height: Option<u32>,
|
||||||
|
pub column_sizes: Option<Vec<u32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct DashboardRowItem {
|
||||||
|
pub id: String,
|
||||||
|
}
|
|
@ -58,53 +58,37 @@ pub async fn get_metric(metric_id: &Uuid, user_id: &Uuid) -> Result<BusterMetric
|
||||||
_ => anyhow!("Database error: {}", e),
|
_ => anyhow!("Database error: {}", e),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Join the content lines into a single YAML string
|
// Extract fields directly from the JSON content
|
||||||
let yaml_content = metric_file
|
let content = &metric_file.content;
|
||||||
.content
|
|
||||||
.as_array()
|
let title = content
|
||||||
.ok_or_else(|| anyhow!("Invalid content format"))?
|
|
||||||
.iter()
|
|
||||||
.filter_map(|line| line.get("text").and_then(Value::as_str))
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
// Parse the YAML content into a Value
|
|
||||||
let yaml_value: Value = serde_yaml::from_str(&yaml_content)
|
|
||||||
.map_err(|e| anyhow!("Failed to parse YAML content: {}", e))?;
|
|
||||||
|
|
||||||
// Extract fields from the YAML
|
|
||||||
let title = yaml_value
|
|
||||||
.get("title")
|
.get("title")
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.unwrap_or("Untitled")
|
.unwrap_or("Untitled")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let description = yaml_value
|
let description = content
|
||||||
.get("description")
|
.get("description")
|
||||||
.and_then(|v| match v {
|
.and_then(|v| match v {
|
||||||
Value::Null => None,
|
Value::Null => None,
|
||||||
v => v.as_str().map(String::from),
|
v => v.as_str().map(String::from),
|
||||||
});
|
});
|
||||||
|
|
||||||
let sql = yaml_value
|
let sql = content
|
||||||
.get("sql")
|
.get("sql")
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
// Parse chart config
|
// Get chart config directly
|
||||||
let chart_config = yaml_value.get("chart_config").cloned().unwrap_or(json!({}));
|
let chart_config = content.get("chart_config").cloned().unwrap_or(json!({}));
|
||||||
|
|
||||||
// Parse data metadata if it exists
|
// Parse data metadata if it exists
|
||||||
let data_metadata = yaml_value.get("data_metadata").map(|metadata| {
|
let data_metadata = content.get("data_metadata").map(|metadata| {
|
||||||
DataMetadata {
|
DataMetadata {
|
||||||
column_count: metadata
|
column_count: metadata.as_array().map(|arr| arr.len() as i32).unwrap_or(1),
|
||||||
.get("column_count")
|
|
||||||
.and_then(Value::as_i64)
|
|
||||||
.unwrap_or(1) as i32,
|
|
||||||
column_metadata: metadata
|
column_metadata: metadata
|
||||||
.get("column_metadata")
|
.as_array()
|
||||||
.and_then(Value::as_array)
|
|
||||||
.map(|columns| {
|
.map(|columns| {
|
||||||
columns
|
columns
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -114,22 +98,28 @@ pub async fn get_metric(metric_id: &Uuid, user_id: &Uuid) -> Result<BusterMetric
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.unwrap_or("unknown")
|
.unwrap_or("unknown")
|
||||||
.to_string(),
|
.to_string(),
|
||||||
min_value: MinMaxValue::Number(0.0), // You might want to parse this from the YAML
|
min_value: MinMaxValue::Number(0.0), // Default value
|
||||||
max_value: MinMaxValue::Number(0.0), // You might want to parse this from the YAML
|
max_value: MinMaxValue::Number(0.0), // Default value
|
||||||
unique_values: col
|
unique_values: 0, // Default value
|
||||||
.get("unique_values")
|
simple_type: match col.get("data_type").and_then(Value::as_str) {
|
||||||
.and_then(Value::as_i64)
|
Some("string") => SimpleType::Text,
|
||||||
.unwrap_or(0) as i32,
|
Some("number") => SimpleType::Number,
|
||||||
simple_type: SimpleType::Number, // You might want to parse this from the YAML
|
Some("boolean") => SimpleType::Boolean,
|
||||||
column_type: ColumnType::Number, // You might want to parse this from the YAML
|
Some("date") => SimpleType::Date,
|
||||||
|
_ => SimpleType::Number,
|
||||||
|
},
|
||||||
|
column_type: match col.get("data_type").and_then(Value::as_str) {
|
||||||
|
Some("string") => ColumnType::Text,
|
||||||
|
Some("number") => ColumnType::Number,
|
||||||
|
Some("boolean") => ColumnType::Boolean,
|
||||||
|
Some("date") => ColumnType::Date,
|
||||||
|
_ => ColumnType::Number,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
row_count: metadata
|
row_count: 1, // Default value since it's not in your JSON structure
|
||||||
.get("row_count")
|
|
||||||
.and_then(Value::as_i64)
|
|
||||||
.unwrap_or(1) as i32,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -141,9 +131,9 @@ pub async fn get_metric(metric_id: &Uuid, user_id: &Uuid) -> Result<BusterMetric
|
||||||
version_number: 1,
|
version_number: 1,
|
||||||
description,
|
description,
|
||||||
file_name: metric_file.file_name,
|
file_name: metric_file.file_name,
|
||||||
time_frame: "all".to_string(), // Default value
|
time_frame: "TODO".to_string(),
|
||||||
dataset_id: "".to_string(), // Would need to be populated if required
|
dataset_id: "TODO".to_string(),
|
||||||
data_source_id: "".to_string(), // Would need to be populated if required
|
data_source_id: "TODO".to_string(),
|
||||||
dataset_name: None,
|
dataset_name: None,
|
||||||
error: None,
|
error: None,
|
||||||
chart_config: Some(chart_config),
|
chart_config: Some(chart_config),
|
||||||
|
@ -151,11 +141,11 @@ pub async fn get_metric(metric_id: &Uuid, user_id: &Uuid) -> Result<BusterMetric
|
||||||
status: metric_file.verification,
|
status: metric_file.verification,
|
||||||
evaluation_score: metric_file.evaluation_score.map(|score| score.to_string()),
|
evaluation_score: metric_file.evaluation_score.map(|score| score.to_string()),
|
||||||
evaluation_summary: metric_file.evaluation_summary.unwrap_or_default(),
|
evaluation_summary: metric_file.evaluation_summary.unwrap_or_default(),
|
||||||
file: yaml_content, // Store the original YAML content
|
file: serde_json::to_string(&content).unwrap_or_default(),
|
||||||
created_at: metric_file.created_at.to_string(),
|
created_at: metric_file.created_at.to_string(),
|
||||||
updated_at: metric_file.updated_at.to_string(),
|
updated_at: metric_file.updated_at.to_string(),
|
||||||
sent_by_id: metric_file.created_by.to_string(),
|
sent_by_id: metric_file.created_by.to_string(),
|
||||||
sent_by_name: "".to_string(), // Would need to join with users table to get this
|
sent_by_name: "".to_string(),
|
||||||
sent_by_avatar_url: None,
|
sent_by_avatar_url: None,
|
||||||
code: None,
|
code: None,
|
||||||
dashboards: vec![],
|
dashboards: vec![],
|
||||||
|
|
|
@ -94,6 +94,8 @@ pub enum SimpleType {
|
||||||
Number,
|
Number,
|
||||||
#[serde(rename = "date")]
|
#[serde(rename = "date")]
|
||||||
Date,
|
Date,
|
||||||
|
#[serde(rename = "boolean")]
|
||||||
|
Boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod metric_files;
|
pub mod metric_files;
|
||||||
pub mod dashboard_files;
|
pub mod dashboard_files;
|
||||||
|
|
||||||
pub use metric_files::*;
|
pub use metric_files::*;
|
||||||
|
pub use dashboard_files::*;
|
|
@ -6,6 +6,7 @@ use diesel_async::RunQueryDsl;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tokio;
|
use tokio;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use serde_yaml;
|
||||||
|
|
||||||
use crate::messages::types::ThreadMessage;
|
use crate::messages::types::ThreadMessage;
|
||||||
use crate::threads::types::ThreadWithMessages;
|
use crate::threads::types::ThreadWithMessages;
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::database_dep::models::User;
|
||||||
|
use crate::routes::rest::ApiResponse;
|
||||||
|
use axum::extract::Path;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::Extension;
|
||||||
|
use handlers::files::dashboard_files::get_dashboard::get_dashboard;
|
||||||
|
use handlers::files::dashboard_files::BusterDashboardResponse;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub async fn get_dashboard_rest_handler(
|
||||||
|
Extension(user): Extension<User>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
) -> Result<ApiResponse<BusterDashboardResponse>, (StatusCode, &'static str)> {
|
||||||
|
let dashboard = match get_dashboard(&id, &user.id).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error getting dashboard: {}", e);
|
||||||
|
return Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to get dashboard"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ApiResponse::JsonData(dashboard))
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
use axum::{
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Placeholder modules that you'll need to create
|
||||||
|
mod get_dashboard;
|
||||||
|
|
||||||
|
pub fn router() -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/:id", get(get_dashboard::get_dashboard_rest_handler))
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ use handlers::files::metric_files::types::BusterMetric;
|
||||||
use handlers::files::metric_files::helpers::get_metric::get_metric;
|
use handlers::files::metric_files::helpers::get_metric::get_metric;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub async fn get_metrics_rest_handler(
|
pub async fn get_metric_rest_handler(
|
||||||
Extension(user): Extension<User>,
|
Extension(user): Extension<User>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<ApiResponse<BusterMetric>, (StatusCode, &'static str)> {
|
) -> Result<ApiResponse<BusterMetric>, (StatusCode, &'static str)> {
|
|
@ -4,9 +4,9 @@ use axum::{
|
||||||
};
|
};
|
||||||
|
|
||||||
// Placeholder modules that you'll need to create
|
// Placeholder modules that you'll need to create
|
||||||
mod get_metrics;
|
mod get_metric;
|
||||||
|
|
||||||
pub fn router() -> Router {
|
pub fn router() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/:id", get(get_metrics::get_metrics_rest_handler))
|
.route("/:id", get(get_metric::get_metric_rest_handler))
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ mod organizations;
|
||||||
mod permission_groups;
|
mod permission_groups;
|
||||||
mod sql;
|
mod sql;
|
||||||
mod users;
|
mod users;
|
||||||
|
mod dashboards;
|
||||||
|
|
||||||
use axum::{middleware, Router};
|
use axum::{middleware, Router};
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ pub fn router() -> Router {
|
||||||
.nest("/organizations", organizations::router())
|
.nest("/organizations", organizations::router())
|
||||||
.nest("/chats", chats::router())
|
.nest("/chats", chats::router())
|
||||||
.nest("/metrics", metrics::router())
|
.nest("/metrics", metrics::router())
|
||||||
|
.nest("/dashboards", dashboards::router())
|
||||||
.route_layer(middleware::from_fn(auth)),
|
.route_layer(middleware::from_fn(auth)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue