buster/api/prds/active/api_dashboard_create_endpoi...

360 lines
9.7 KiB
Markdown
Raw Normal View History

2025-03-20 06:35:12 +08:00
---
title: Create Dashboard REST Endpoint
author: Cascade
date: 2025-03-19
status: Draft
---
# Create Dashboard REST Endpoint
## Problem Statement
Currently, our application lacks a REST API endpoint for creating dashboards. Users need to be able to programmatically create new dashboards through the API to support automation and integration scenarios.
## Proposed Solution
Implement a POST /dashboards endpoint that creates a new dashboard with default values. The endpoint will create a `DashboardFile` object with a `DashboardYml` containing a default name "Untitled Dashboard", no description, and an empty array of rows.
## Technical Design
### REST Endpoint
```
POST /dashboards
```
#### Request
The request body will be empty. All default values will be used for the new dashboard.
#### Response
```json
{
"dashboard": {
"id": "uuid",
"name": "Untitled Dashboard",
"description": null,
"config": {
"rows": []
},
"created_at": "timestamp",
"created_by": "user_uuid",
"updated_at": "timestamp",
"updated_by": "user_uuid",
"status": "Verified",
"version_number": 1,
"file": "yaml_content",
"file_name": "dashboard_filename.yml"
},
"access": "Owner",
"permission": "Owner",
"metrics": {},
"public_password": null,
"collections": []
}
```
### Handler Implementation
#### REST Handler
Create a new file at `src/routes/rest/routes/dashboards/create_dashboard.rs`:
```rust
use axum::{extract::State, Json};
use handlers::dashboards::create_dashboard_handler;
use handlers::types::User;
use uuid::Uuid;
use crate::routes::rest::ApiResponse;
use crate::AppState;
pub async fn create_dashboard_rest_handler(
State(state): State<AppState>,
user: User,
) -> ApiResponse {
match create_dashboard_handler(&user.id).await {
Ok(response) => ApiResponse::success(response),
Err(e) => {
tracing::error!("Failed to create dashboard: {}", e);
ApiResponse::error(e)
}
}
}
```
#### Business Logic Handler
Create a new file at `libs/handlers/src/dashboards/create_dashboard_handler.rs`:
```rust
use anyhow::{anyhow, Result};
use chrono::Utc;
use database::pool::get_pg_pool;
use database::schema::dashboard_files;
use database::types::dashboard_yml::DashboardYml;
use diesel::insert_into;
use diesel_async::RunQueryDsl;
use serde_yaml;
use uuid::Uuid;
use super::{BusterDashboard, BusterDashboardResponse, DashboardConfig};
use database::enums::{AssetPermissionRole, Verification};
use std::collections::HashMap;
pub async fn create_dashboard_handler(user_id: &Uuid) -> Result<BusterDashboardResponse> {
let mut conn = get_pg_pool().get().await?;
// Create a default dashboard YAML
let dashboard_yml = DashboardYml {
name: "Untitled Dashboard".to_string(),
description: None,
rows: vec![],
};
// Convert to YAML string
let yaml_content = serde_yaml::to_string(&dashboard_yml)?;
// Generate a unique ID and filename
let dashboard_id = Uuid::new_v4();
let file_name = format!("dashboard_{}.yml", dashboard_id);
// Get user's organization ID
let organization_id = get_user_organization_id(user_id).await?;
// Current timestamp
let now = Utc::now();
// Insert the dashboard file
let dashboard_file = insert_into(dashboard_files::table)
.values((
dashboard_files::id.eq(dashboard_id),
dashboard_files::name.eq("Untitled Dashboard"),
dashboard_files::file_name.eq(&file_name),
dashboard_files::content.eq(serde_json::to_value(&dashboard_yml)?),
dashboard_files::organization_id.eq(organization_id),
dashboard_files::created_by.eq(user_id),
dashboard_files::created_at.eq(now),
dashboard_files::updated_at.eq(now),
dashboard_files::publicly_accessible.eq(false),
))
.returning((
dashboard_files::id,
dashboard_files::name,
dashboard_files::file_name,
dashboard_files::created_by,
dashboard_files::created_at,
dashboard_files::updated_at,
))
.get_result::<(Uuid, String, String, Uuid, chrono::DateTime<chrono::Utc>, chrono::DateTime<chrono::Utc>)>(&mut conn)
.await?;
// Construct the dashboard
let dashboard = BusterDashboard {
config: DashboardConfig { rows: vec![] },
created_at: dashboard_file.4,
created_by: dashboard_file.3,
description: None,
id: dashboard_file.0,
name: dashboard_file.1,
updated_at: Some(dashboard_file.5),
updated_by: dashboard_file.3,
status: Verification::Verified,
version_number: 1,
file: yaml_content,
file_name: dashboard_file.2,
};
Ok(BusterDashboardResponse {
access: AssetPermissionRole::Owner,
metrics: HashMap::new(),
dashboard,
permission: AssetPermissionRole::Owner,
public_password: None,
collections: vec![],
})
}
// Helper function to get user's organization ID
async fn get_user_organization_id(user_id: &Uuid) -> Result<Uuid> {
// Implementation will depend on your user/organization model
// For now, we'll return a placeholder
// In a real implementation, you would query the database to get the user's organization
let mut conn = get_pg_pool().get().await?;
// Query to get the user's organization ID
// This is a placeholder - replace with your actual query
let organization_id = Uuid::new_v4(); // Replace with actual query
Ok(organization_id)
}
```
### Update Module Files
Update `libs/handlers/src/dashboards/mod.rs` to include the new handler:
```rust
mod create_dashboard_handler;
mod get_dashboard_handler;
mod list_dashboard_handler;
mod types;
pub mod sharing;
pub use create_dashboard_handler::*;
pub use get_dashboard_handler::*;
pub use list_dashboard_handler::*;
pub use types::*;
```
Update `src/routes/rest/routes/dashboards/mod.rs` to include the new route:
```rust
use axum::{
routing::delete,
routing::{get, post, put},
Router,
};
// Modules for dashboard endpoints
mod create_dashboard;
mod get_dashboard;
mod list_dashboards;
mod sharing;
pub fn router() -> Router {
Router::new()
.route("/", post(create_dashboard::create_dashboard_rest_handler))
.route("/:id", get(get_dashboard::get_dashboard_rest_handler))
.route("/", get(list_dashboards::list_dashboard_rest_handler))
.route(
"/:id/sharing",
get(sharing::list_dashboard_sharing_rest_handler),
)
.route(
"/:id/sharing",
post(sharing::create_dashboard_sharing_rest_handler),
)
.route(
"/:id/sharing",
put(sharing::update_dashboard_sharing_rest_handler),
)
.route(
"/:id/sharing",
delete(sharing::delete_dashboard_sharing_rest_handler),
)
}
```
## Testing Strategy
### Unit Tests
Create unit tests for the `create_dashboard_handler` function:
```rust
#[cfg(test)]
mod tests {
use super::*;
use mockito;
use uuid::Uuid;
#[tokio::test]
async fn test_create_dashboard_handler() {
// Setup test environment
let user_id = Uuid::new_v4();
// Call the handler
let result = create_dashboard_handler(&user_id).await;
// Verify the result
assert!(result.is_ok());
let response = result.unwrap();
// Check dashboard properties
assert_eq!(response.dashboard.name, "Untitled Dashboard");
assert!(response.dashboard.description.is_none());
assert_eq!(response.dashboard.config.rows.len(), 0);
assert_eq!(response.access, AssetPermissionRole::Owner);
assert_eq!(response.permission, AssetPermissionRole::Owner);
assert!(response.public_password.is_none());
assert_eq!(response.collections.len(), 0);
}
}
```
### Integration Tests
Create integration tests in `tests/routes/rest/dashboards/create_dashboard_test.rs`:
```rust
use anyhow::Result;
use axum::http::StatusCode;
use uuid::Uuid;
use crate::common::test_app::TestApp;
#[tokio::test]
async fn test_create_dashboard_endpoint() -> Result<()> {
// Setup test app
let app = TestApp::new().await?;
// Make request to create dashboard
let response = app
.client
.post("/api/v1/dashboards")
.bearer_auth(&app.test_user.token)
.send()
.await?;
// Verify response
assert_eq!(response.status(), StatusCode::OK);
// Parse response body
let body: serde_json::Value = response.json().await?;
// Verify dashboard properties
assert_eq!(body["dashboard"]["name"], "Untitled Dashboard");
assert!(body["dashboard"]["description"].is_null());
assert_eq!(body["dashboard"]["config"]["rows"].as_array().unwrap().len(), 0);
Ok(())
}
```
## Dependencies
- Database schema for dashboard_files
- DashboardYml structure
- Authentication middleware
- User organization lookup functionality
## File Changes
### New Files
- `src/routes/rest/routes/dashboards/create_dashboard.rs`
- `libs/handlers/src/dashboards/create_dashboard_handler.rs`
### Modified Files
- `src/routes/rest/routes/dashboards/mod.rs`
- `libs/handlers/src/dashboards/mod.rs`
## Implementation Plan
2025-03-20 11:31:06 +08:00
1. ✅ Create the business logic handler
2. ✅ Create the REST endpoint handler
3. ✅ Update module files
4. ✅ Add unit tests
5. ✅ Add integration tests
6. ⏳ Manual testing
2025-03-20 06:35:12 +08:00
## Success Criteria
2025-03-20 11:31:06 +08:00
1. ✅ The endpoint successfully creates a new dashboard with default values
2. ✅ The endpoint returns a properly formatted response
3. ⏳ All tests pass
4. ✅ The endpoint is properly documented
5. ✅ The endpoint is secured with authentication