Skip to content

feat: dashboard implementation #1305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

nikhilsinhaparseable
Copy link
Contributor

@nikhilsinhaparseable nikhilsinhaparseable commented Apr 30, 2025

Summary by CodeRabbit

  • New Features

    • Added the ability to add tiles to existing dashboards via a new API endpoint.
    • Dashboards now display a modification timestamp and author information.
    • Introduced a unified search endpoint on the home page to filter and retrieve alerts, correlations, dashboards, filters, and streams.
  • Refactor

    • Dashboard and tile identifiers standardized using ULIDs for consistency.
    • Dashboard and tile models simplified by removing complex nested structures and version migrations.
    • Dashboard version reset to "v1" with legacy migration logic removed.
    • Ownership verification added for dashboard updates and deletions.
    • Dashboard listing and retrieval functions simplified, no longer requiring session keys or user filtering.
    • Dashboard routes reorganized to group related operations under dashboard-specific scopes.
    • Client now responsible for providing unique tile IDs when updating dashboards.
    • Home response simplified by reducing returned data and restructuring resource representation.
  • Bug Fixes

    • Improved error handling for invalid dashboard identifiers.
    • Added proper authorization error responses with HTTP 401 status.
    • Added validation and error handling for search query parameters with HTTP 400 responses.

Copy link
Contributor

coderabbitai bot commented Apr 30, 2025

"""

Walkthrough

The changes refactor the dashboard feature by standardizing all dashboard and tile identifiers to use ULIDs instead of custom string hashes. The dashboard data model is simplified, removing complex nested structures, version migrations, and user-based filtering. The HTTP handler functions are updated to validate and use ULIDs, and session key extraction is removed from their signatures. The dashboard storage logic is streamlined, discarding version migrations and manual ID assignments. Function signatures and internal logic are adjusted throughout to reflect these updates, resulting in a flatter, more concise dashboard management flow. A new endpoint to add tiles to existing dashboards is introduced with authorization checks.

Changes

File(s) Change Summary
src/handlers/http/users/dashboards.rs Refactored dashboard and tile ID handling to use ULIDs; removed session key extraction; updated function signatures; removed user ID-based dashboard filtering; simplified ID assignment and validation; removed manual storage calls; added new add_tile endpoint for adding tiles to dashboards; extended DashboardError with Unauthorized variant returning HTTP 401.
src/prism/home/mod.rs Removed several structs and fields from HomeResponse; simplified generate_home_response to fewer fields; added new search API generate_home_search_response returning unified Resource list filtered by query; replaced title fetching helpers to support query filtering and return Resource structs; added InvalidQueryParameter error variant.
src/users/dashboards.rs Simplified dashboard data model; removed complex nested structs and enums; replaced with minimal Tile struct; changed dashboard version to "v1"; removed migration logic and user-based filtering; updated methods to use ULIDs for IDs; removed version migration functions; added methods for create, update, delete, get by ID and by user with authorization checks; added validate_dashboard_id function.
src/handlers/http/modal/server.rs Changed get_prism_home from returning a single resource to a scoped route with /home and /home/search; renamed dashboard handler functions for clarity; nested dashboard routes under /{dashboard_id} scope; preserved GET, DELETE, PUT routes; added new PUT /add_tile route with authorization for Action::CreateDashboard.
src/handlers/http/prism_home.rs Added new async handler home_search extracting session key and query parameters, validating presence of "key" query parameter, calling generate_home_search_response, and returning JSON response; added error handling for missing or invalid query; updated imports and constants accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HTTP_Handler
    participant Dashboards_Store

    Client->>HTTP_Handler: Request (list/get/create/update/delete/add_tile dashboard)
    HTTP_Handler->>Dashboards_Store: Validate/parse ULID(s)
    HTTP_Handler->>Dashboards_Store: Perform action (list, get, create, update, delete, add tile)
    Dashboards_Store-->>HTTP_Handler: Dashboard(s) data (using ULIDs)
    HTTP_Handler-->>Client: Response (dashboard(s) data or status)
Loading

Possibly related PRs

  • revamp home api #1307: Introduces /home/search endpoint and restructures /home route as a scope, closely related to the changes in src/handlers/http/modal/server.rs and src/handlers/http/prism_home.rs.

Suggested labels

for next release

Poem

🐇
Hopping through the dashboard fields,
With ULIDs shining, old hashes yield.
Out with the clutter, in with the new,
Flat and simple, easy to view.
No more sessions or tangled code—
Just clean IDs on every road!
—Rabbit, with a spring in its step
"""

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🔭 Outside diff range comments (1)
src/prism/home/mod.rs (1)

93-109: 🛠️ Refactor suggestion

⚠️ Potential issue

Removal of authorisation filter exposes all dashboards to every user

get_dashboard_titles() is now invoked without a SessionKey, so the home page will show titles/ids of dashboards belonging to all users.
This is a regression from the previous behaviour (filtered by SessionKey) and leaks potentially sensitive metadata.

Consider either:

  1. Restoring a keyed list_dashboards(key) variant, or
  2. Filtering the returned dashboards by dashboard.author == key.user_hash after the call.

Failing to do so turns an authenticated-but-isolated view into a global, privacy-breaking one.

🧹 Nitpick comments (2)
src/users/dashboards.rs (2)

38-43: Inconsistent ID types between Tile and Layout

Tile.tile_id is a Ulid, but Layout.tile_id is an Option<String>.
This increases the risk of runtime conversions and invalid data sneaking in.

Unless there is a compelling reason, store the same concrete type:

-pub struct Layout {
-    pub tile_id: Option<String>,
+pub struct Layout {
+    pub tile_id: Option<Ulid>,

(Serde will serialise Ulid to/from strings automatically.)


102-111: list_dashboards clones every dashboard – consider returning references

For read-heavy workloads the full clone can become expensive.
You could return Arc<Dashboard> or borrow-guarded slices instead, e.g.:

pub async fn list_dashboards(&self) -> Vec<Dashboard> {
    self.0.read().await.clone()
}

Not critical but may help with memory pressure.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a90ed13 and 4a7155e.

📒 Files selected for processing (3)
  • src/handlers/http/users/dashboards.rs (4 hunks)
  • src/prism/home/mod.rs (2 hunks)
  • src/users/dashboards.rs (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: Build Default x86_64-pc-windows-msvc
  • GitHub Check: Build Default aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-apple-darwin
  • GitHub Check: Build Kafka aarch64-apple-darwin
  • GitHub Check: Build Default aarch64-unknown-linux-gnu
  • GitHub Check: Build Kafka x86_64-unknown-linux-gnu
  • GitHub Check: Quest Smoke and Load Tests for Standalone deployments
  • GitHub Check: Build Default x86_64-unknown-linux-gnu
  • GitHub Check: coverage
  • GitHub Check: Quest Smoke and Load Tests for Distributed deployments

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
src/users/dashboards.rs (1)

59-78: ⚠️ Potential issue

load() never purges dashboards removed from storage → the in-memory index drifts.

Dashboards::load() accumulates by s.append(&mut this) after the write-lock.
If an object is deleted in the bucket, this simply omits it, leaving the stale copy already present in self.0.

Replace the append with a full swap so RAM reflects the source-of-truth:

 let mut s = self.0.write().await;
-s.append(&mut this);
+*s = this;

Also consider returning the old vector (or its length) for observability.

♻️ Duplicate comments (4)
src/handlers/http/users/dashboards.rs (4)

38-60: ⚠️ Potential issue

list() leaks all dashboards to every caller and may panic on modified.unwrap().

  1. Ownership / tenancy checks that were requested in previous reviews are still missing – any authenticated user can enumerate dashboards system-wide.
  2. dashboard.modified.unwrap() will crash for dashboards created before this field existed.
-let dashboards: Vec<Map<_, _>> = dashboards
-    .iter()
-    .map(|dashboard| {
-        let mut map = Map::new();
-
-        map.insert(
-            "modified".to_string(),
-            serde_json::Value::String(dashboard.modified.unwrap().to_string()),
-        );
-
-    })
-    .collect();
+let caller = get_hash(&get_user_from_request(&req)?);
+let dashboards: Vec<Map<_, _>> = dashboards
+    .into_iter()
+    .filter(|d| d.author == caller)
+    .map(|d| {
+        let mut map = Map::new();
+        map.insert("title".into(), d.title.into());
+        map.insert("author".into(), d.author.into());
+        map.insert(
+            "modified".into(),
+            d.modified
+                .map(|ts| ts.to_rfc3339())
+                .unwrap_or_default()
+                .into(),
+        );
+        map
+    })
+    .collect();

This closes the multi-tenant data leak (previously reported) and removes the potential panic.


111-123: ⚠️ Potential issue

update() still lets a user hijack another user’s dashboard.

You only check existence, not ownership, then overwrite dashboard.author with the caller’s hash.
See prior reviewer notes – the attack vector is unchanged.

Add an explicit author match before allowing the update.


148-154: ⚠️ Potential issue

delete() repeats the cross-tenant deletion defect.

Same pattern: absence of existing.author == caller check allows any user to evict another user’s dashboard from the in-memory cache (while leaving stale objects in storage).

Apply the author check analogue to the one suggested for update().


165-189: 🛠️ Refactor suggestion

add_tile lacks ownership validation and rewrites dashboard.version blindly.

  • Missing dashboard.author == caller check → tile injection into someone else’s board.
  • Overwriting version on every call loses history and hinders concurrent edits.

Add an author guard and only bump modified, not the semantic version (or introduce a revision field).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a7155e and 8c4c415.

📒 Files selected for processing (3)
  • src/handlers/http/modal/server.rs (1 hunks)
  • src/handlers/http/users/dashboards.rs (4 hunks)
  • src/users/dashboards.rs (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: Build Default x86_64-pc-windows-msvc
  • GitHub Check: Build Default aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-apple-darwin
  • GitHub Check: Build Kafka aarch64-apple-darwin
  • GitHub Check: Build Default aarch64-unknown-linux-gnu
  • GitHub Check: Quest Smoke and Load Tests for Standalone deployments
  • GitHub Check: Build Kafka x86_64-unknown-linux-gnu
  • GitHub Check: Quest Smoke and Load Tests for Distributed deployments
  • GitHub Check: coverage
  • GitHub Check: Build Default x86_64-unknown-linux-gnu

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (1)
src/handlers/http/users/dashboards.rs (1)

63-74: ⚠️ Potential issue

Security concern: get() function allows cross-tenant access

Similar to the list() function, the get() function retrieves a dashboard without verifying that the requester is the owner. This allows any user to access dashboards they don't own if they know the dashboard ID.

-pub async fn get(dashboard_id: Path<String>) -> Result<impl Responder, DashboardError> {
+pub async fn get(req: HttpRequest, dashboard_id: Path<String>) -> Result<impl Responder, DashboardError> {
+    let user_id = get_hash(&get_user_from_request(&req)?);
     let dashboard_id = if let Ok(dashboard_id) = Ulid::from_string(&dashboard_id.into_inner()) {
         dashboard_id
     } else {
         return Err(DashboardError::Metadata("Invalid dashboard ID"));
     };

     if let Some(dashboard) = DASHBOARDS.get_dashboard(dashboard_id).await {
+        if dashboard.author.as_ref().map_or(true, |author| author != &user_id) {
+            return Err(DashboardError::Metadata("Unauthorized to access dashboard"));
+        }
         return Ok((web::Json(dashboard), StatusCode::OK));
     }
♻️ Duplicate comments (3)
src/handlers/http/users/dashboards.rs (3)

117-122: ⚠️ Potential issue

Security concern: update() allows hijacking another user's dashboard

The current implementation checks if the dashboard exists but not if the user making the request is the owner. Additionally, it overwrites the author field with the current user's hash, effectively allowing a user to take ownership of any dashboard.

-    if DASHBOARDS.get_dashboard(dashboard_id).await.is_none() {
-        return Err(DashboardError::Metadata("Dashboard does not exist"));
-    }
-    dashboard.dashboard_id = Some(dashboard_id);
-    dashboard.author = Some(user_id.clone());
+    let existing = DASHBOARDS.get_dashboard(dashboard_id)
+        .await
+        .ok_or(DashboardError::Metadata("Dashboard does not exist"))?;
+    
+    if existing.author.as_ref().map_or(true, |author| author != &user_id) {
+        return Err(DashboardError::Metadata("Unauthorized to modify dashboard"));
+    }
+    
+    dashboard.dashboard_id = Some(dashboard_id);
+    dashboard.author = existing.author;

148-160: ⚠️ Potential issue

Security concern: delete() function allows cross-tenant deletion

Similar to other endpoints, the delete() function allows a user to delete any dashboard without verifying ownership.

     let dashboard_id = if let Ok(dashboard_id) = Ulid::from_string(&dashboard_id.into_inner()) {
         dashboard_id
     } else {
         return Err(DashboardError::Metadata("Invalid dashboard ID"));
     };
-    if DASHBOARDS.get_dashboard(dashboard_id).await.is_none() {
-        return Err(DashboardError::Metadata("Dashboard does not exist"));
+    
+    let existing = DASHBOARDS.get_dashboard(dashboard_id)
+        .await
+        .ok_or(DashboardError::Metadata("Dashboard does not exist"))?;
+    
+    if existing.author.as_ref().map_or(true, |author| author != &user_id) {
+        return Err(DashboardError::Metadata("Unauthorized to delete dashboard"));
     }
     let path = dashboard_path(&user_id, &format!("{}.json", dashboard_id));

38-60: 💡 Verification agent

🧩 Analysis chain

Security concern: list() function allows cross-tenant access

The current implementation of list() returns all dashboards in the system without any user-based filtering. This means any authenticated user can access dashboards created by other users, violating multi-tenant security boundaries.

-pub async fn list() -> Result<impl Responder, DashboardError> {
-    let dashboards = DASHBOARDS.list_dashboards().await;
+pub async fn list(req: HttpRequest) -> Result<impl Responder, DashboardError> {
+    let user_id = get_hash(&get_user_from_request(&req)?);
+    let dashboards = DASHBOARDS.list_dashboards().await
+        .into_iter()
+        .filter(|d| d.author.as_ref().map_or(false, |a| a == &user_id))
+        .collect::<Vec<_>>();

🏁 Script executed:

#!/bin/bash
# Checking if there are any access controls for the list endpoint elsewhere in the code
rg -n "list_dashboards.*filter" --type rust

Length of output: 45


Let’s verify how DASHBOARDS and its list_dashboards method are defined to confirm whether tenant‐scoping is already enforced:


🏁 Script executed:

#!/bin/bash
# Locate the DASHBOARDS constant or static
rg -n "DASHBOARDS" --type rust

# Locate definition(s) of list_dashboards
rg -n "fn list_dashboards" --type rust

Length of output: 1899


Let’s inspect the list_dashboards implementation to confirm whether tenant scoping is applied:


🏁 Script executed:

#!/bin/bash
rg -n -A20 'pub async fn list_dashboards' --type rust src/users/dashboards.rs

Length of output: 368


Enforce tenant scoping in list()
The list() handler currently returns every dashboard in memory—there’s no per-user or per-tenant filter. This allows any authenticated user to see dashboards they don’t own.

• File: src/handlers/http/users/dashboards.rs (around lines 38–60)
• Change the signature to accept the request context so you can extract the caller’s user/tenant ID.
• Extract the user ID (e.g. via get_user_from_request(&req)?) and filter the returned dashboards by dashboard.author == user_id.
• Optionally, push this logic into the storage layer as a new method (e.g. list_dashboards_for_user).

Suggested diff:

-pub async fn list() -> Result<impl Responder, DashboardError> {
+pub async fn list(req: HttpRequest) -> Result<impl Responder, DashboardError> {
     // load all dashboards…
     let dashboards = DASHBOARDS.list_dashboards().await;

+    // extract and enforce per-user tenant scope
+    let user_id = get_user_from_request(&req)?;
+    let dashboards = dashboards
+        .into_iter()
+        .filter(|d| d.author.as_ref().map_or(false, |a| a == &user_id))
+        .collect::<Vec<_>>();

     // build JSON response
     let dashboards: Vec<Map<String, serde_json::Value>> = dashboards
         .iter()
         .map(|dashboard| { … })
         .collect();
     Ok((web::Json(dashboards), StatusCode::OK))
🧹 Nitpick comments (1)
src/users/dashboards.rs (1)

31-36: Consider implementing a builder pattern for the Tile struct

The Tile struct has a simplistic design with just a tile_id and other_fields, but creating and managing these objects could be made more ergonomic with a builder pattern.

Consider implementing a builder pattern for the Tile struct to make it easier to create and manipulate tiles:

impl Tile {
    pub fn new(tile_id: Ulid) -> Self {
        Self {
            tile_id,
            other_fields: None,
        }
    }
    
    pub fn with_field(mut self, key: &str, value: Value) -> Self {
        let map = self.other_fields.get_or_insert_with(|| serde_json::Map::new());
        map.insert(key.to_string(), value);
        self
    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c4c415 and 4e4b760.

📒 Files selected for processing (3)
  • src/handlers/http/users/dashboards.rs (4 hunks)
  • src/prism/home/mod.rs (2 hunks)
  • src/users/dashboards.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/prism/home/mod.rs
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: Build Kafka aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-pc-windows-msvc
  • GitHub Check: Build Kafka x86_64-unknown-linux-gnu
  • GitHub Check: Build Default aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-apple-darwin
  • GitHub Check: Quest Smoke and Load Tests for Standalone deployments
  • GitHub Check: Build Default aarch64-unknown-linux-gnu
  • GitHub Check: Build Default x86_64-unknown-linux-gnu
  • GitHub Check: coverage
  • GitHub Check: Quest Smoke and Load Tests for Distributed deployments

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (7)
src/users/dashboards.rs (3)

162-169: ⚠️ Potential issue

Potential null reference in get_dashboard

Similar to the delete method, this function unconditionally unwraps the optional dashboard_id field, which could lead to runtime panics.

Use a safer approach with map_or:

 pub async fn get_dashboard(&self, dashboard_id: Ulid) -> Option<Dashboard> {
     self.0
         .read()
         .await
         .iter()
-        .find(|d| *d.dashboard_id.as_ref().unwrap() == dashboard_id)
+        .find(|d| d.dashboard_id.as_ref().map_or(false, |id| *id == dashboard_id))
         .cloned()
 }

154-154: ⚠️ Potential issue

Potential null reference in retain clause

The unwrap() call on dashboard_id could lead to runtime panics if any dashboard has a null ID.

Use a safer approach with map_or:

-s.retain(|d| *d.dashboard_id.as_ref().unwrap() != dashboard_id);
+s.retain(|d| d.dashboard_id.as_ref().map_or(true, |id| *id != dashboard_id));

48-57: 🛠️ Refactor suggestion

Simplified Dashboard struct with optional fields

The refactored dashboard structure removes several complex fields and uses optional types for flexibility. However, without proper initialization safeguards, this could lead to runtime panics when accessing these optional fields.

Implement a constructor method to ensure all fields are properly initialized:

 pub struct Dashboard {
     pub version: Option<String>,
     pub title: String,
     pub author: Option<String>,
     pub dashboard_id: Option<Ulid>,
     pub modified: Option<DateTime<Utc>>,
     dashboard_type: Option<DashboardType>,
     pub tiles: Vec<Tile>,
+}
+
+impl Dashboard {
+    pub fn new(title: String) -> Self {
+        Self {
+            version: Some(CURRENT_DASHBOARD_VERSION.to_string()),
+            title,
+            author: None,
+            dashboard_id: Some(Ulid::new()),
+            modified: Some(Utc::now()),
+            dashboard_type: Some(DashboardType::Dashboard),
+            tiles: Vec::new(),
+        }
+    }
+    
+    pub fn with_author(mut self, author: String) -> Self {
+        self.author = Some(author);
+        self
+    }
+    
+    pub fn add_tile(&mut self, tile: Tile) {
+        self.tiles.push(tile);
+        self.modified = Some(Utc::now());
+    }
 }
src/handlers/http/users/dashboards.rs (4)

59-68: ⚠️ Potential issue

Security issue: The get endpoint bypasses ownership checks

The get handler doesn't verify if the requesting user is the author of the dashboard, allowing unauthorized access to dashboards.

Add authorization checks:

-pub async fn get(dashboard_id: Path<String>) -> Result<impl Responder, DashboardError> {
+pub async fn get(req: HttpRequest, dashboard_id: Path<String>) -> Result<impl Responder, DashboardError> {
+    let mut user_id = get_user_from_request(&req)?;
+    user_id = get_hash(&user_id);
     let dashboard_id = if let Ok(dashboard_id) = Ulid::from_string(&dashboard_id.into_inner()) {
         dashboard_id
     } else {
         return Err(DashboardError::Metadata("Invalid dashboard ID"));
     };

     if let Some(dashboard) = DASHBOARDS.get_dashboard(dashboard_id).await {
+        if dashboard.author.as_deref() != Some(&user_id) {
+            return Err(DashboardError::Metadata("Unauthorized to access dashboard"));
+        }
         return Ok((web::Json(dashboard), StatusCode::OK));
     }

92-97: ⚠️ Potential issue

Security issue: The update endpoint could hijack dashboards

The update handler sets the author field to the current user without verifying if the requesting user is the original author, allowing users to hijack dashboards created by others.

Check dashboard ownership before updating:

     let dashboard_id = if let Ok(dashboard_id) = Ulid::from_string(&dashboard_id.into_inner()) {
         dashboard_id
     } else {
         return Err(DashboardError::Metadata("Invalid dashboard ID"));
     };
-    dashboard.author = Some(user_id.clone());
+    
+    // Check if dashboard exists and user is the author
+    let existing = DASHBOARDS
+        .get_dashboard(dashboard_id)
+        .await
+        .ok_or(DashboardError::Metadata("Dashboard does not exist"))?;
+    
+    if existing.author.as_deref() != Some(&user_id) {
+        return Err(DashboardError::Metadata("Unauthorized to modify dashboard"));
+    }
+    
+    // Preserve original author
+    dashboard.author = existing.author;

111-116: ⚠️ Potential issue

Security issue: The delete endpoint lacks ownership verification

The delete handler doesn't verify if the requesting user is the author of the dashboard, allowing unauthorized deletion.

Add ownership verification:

     let dashboard_id = if let Ok(dashboard_id) = Ulid::from_string(&dashboard_id.into_inner()) {
         dashboard_id
     } else {
         return Err(DashboardError::Metadata("Invalid dashboard ID"));
     };
+    
+    // Check if dashboard exists and user is the author
+    let existing = DASHBOARDS
+        .get_dashboard(dashboard_id)
+        .await
+        .ok_or(DashboardError::Metadata("Dashboard does not exist"))?;
+    
+    if existing.author.as_deref() != Some(&user_id) {
+        return Err(DashboardError::Metadata("Unauthorized to delete dashboard"));
+    }
+    
     DASHBOARDS.delete_dashboard(&user_id, dashboard_id).await?;

121-146: ⚠️ Potential issue

Security issue: The add_tile endpoint lacks authorization checks

The newly added add_tile handler doesn't verify if the requesting user is the author of the dashboard, allowing unauthorized modifications.

Add authorization checks:

     let mut dashboard = DASHBOARDS
         .get_dashboard(dashboard_id)
         .await
         .ok_or(DashboardError::Metadata("Dashboard does not exist"))?;
+    
+    // Verify that the user is the author of the dashboard
+    if dashboard.author.as_deref() != Some(&user_id) {
+        return Err(DashboardError::Metadata("Unauthorized to modify dashboard"));
+    }
+    
     if tile.tile_id.is_nil() {
         tile.tile_id = Ulid::new();
     }
🧹 Nitpick comments (1)
src/users/dashboards.rs (1)

67-67: Check if deconstruction is necessary

This line is iterating over key-value pairs, but only the values (dashboards) are used inside the loop. Consider simplifying this.

-for (_, dashboards) in all_dashboards {
+for dashboards in all_dashboards.into_values() {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e4b760 and cd58a46.

📒 Files selected for processing (2)
  • src/handlers/http/users/dashboards.rs (4 hunks)
  • src/users/dashboards.rs (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: Build Default x86_64-pc-windows-msvc
  • GitHub Check: Build Default aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-apple-darwin
  • GitHub Check: Build Default aarch64-unknown-linux-gnu
  • GitHub Check: Build Kafka aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-unknown-linux-gnu
  • GitHub Check: Quest Smoke and Load Tests for Distributed deployments
  • GitHub Check: Build Kafka x86_64-unknown-linux-gnu
  • GitHub Check: coverage
  • GitHub Check: Quest Smoke and Load Tests for Standalone deployments
🔇 Additional comments (3)
src/users/dashboards.rs (3)

33-33: Version reset from v3 to v1

The dashboard version constant has been changed from "v3" to "v1". This suggests a reset in the versioning scheme, which is consistent with the simplified data model approach.


35-40: New DashboardType enum looks good

The addition of the DashboardType enum provides a clear categorization system for dashboards.


42-47: Simplified Tile struct with Ulid for tile_id

The refactored Tile struct now uses Ulid for identifiers instead of string hashes and flattens additional properties into a flexible JSON map. This provides better type safety and standardization.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (2)
src/handlers/http/users/dashboards.rs (2)

77-87: 🛠️ Refactor suggestion

The post function should validate the Dashboard before storage

The function properly assigns authorship but doesn't validate the dashboard before storing. Add validation to ensure:

  1. Required fields are present (title at minimum)
  2. Tile IDs are valid ULIDs or get assigned new ones
  3. Dashboard ID is set to a new ULID if not present
    let mut user_id = get_user_from_request(&req)?;
    user_id = get_hash(&user_id);
    dashboard.author = Some(user_id.clone());
+    
+    // Validate required fields
+    if dashboard.title.trim().is_empty() {
+        return Err(DashboardError::Metadata("Dashboard title cannot be empty"));
+    }
+    
+    // Ensure dashboard ID is set
+    if dashboard.dashboard_id.is_none() || dashboard.dashboard_id.unwrap().is_nil() {
+        dashboard.dashboard_id = Some(Ulid::new());
+    }
+    
+    // Ensure all tiles have valid IDs
+    for tile in &mut dashboard.tiles {
+        if tile.tile_id.is_nil() {
+            tile.tile_id = Ulid::new();
+        }
+    }

    DASHBOARDS.create(&user_id, &mut dashboard).await?;

153-183: 🛠️ Refactor suggestion

Consider refactoring common ULID validation into a helper function

The same ULID parsing and validation logic is repeated in multiple places. Extract it to a helper function:

fn validate_dashboard_id(id_str: String) -> Result<Ulid, DashboardError> {
    Ulid::from_string(&id_str)
        .map_err(|_| DashboardError::Metadata("Invalid dashboard ID"))
}

Then use it in all handlers to reduce duplication:

-    let dashboard_id = if let Ok(dashboard_id) = Ulid::from_string(&dashboard_id.into_inner()) {
-        dashboard_id
-    } else {
-        return Err(DashboardError::Metadata("Invalid dashboard ID"));
-    };
+    let dashboard_id = validate_dashboard_id(dashboard_id.into_inner())?;
♻️ Duplicate comments (5)
src/handlers/http/users/dashboards.rs (5)

63-74: ⚠️ Potential issue

Enforce author-bound access in GET /dashboards/{id}

The current handler retrieves and returns any dashboard without checking ownership. This allows any user who knows a valid dashboard ID to access dashboards belonging to other users.

    if let Some(dashboard) = DASHBOARDS.get_dashboard(dashboard_id).await {
+        // Get the current user's ID to verify ownership
+        let mut user_id = get_user_from_request(&req)?;
+        user_id = get_hash(&user_id);
+        
+        // Verify the dashboard belongs to the requesting user
+        if dashboard.author.as_ref().map_or(true, |author| author != &user_id) {
+            return Err(DashboardError::Metadata("Unauthorized to access dashboard"));
+        }
        return Ok((web::Json(dashboard), StatusCode::OK));
    }

You need to:

  1. Add HttpRequest parameter to the function signature
  2. Extract the user_id from the request
  3. Check if the dashboard author matches the requesting user

89-107: ⚠️ Potential issue

update lets a user hijack another user's dashboard

The function doesn't verify that the user updating the dashboard is its owner. This allows users to modify dashboards they don't own.

    let dashboard_id = if let Ok(dashboard_id) = Ulid::from_string(&dashboard_id.into_inner()) {
        dashboard_id
    } else {
        return Err(DashboardError::Metadata("Invalid dashboard ID"));
    };
+    
+    // Verify that the dashboard exists and belongs to the user
+    if let Some(existing) = DASHBOARDS.get_dashboard(dashboard_id).await {
+        if existing.author.as_ref().map_or(true, |author| author != &user_id) {
+            return Err(DashboardError::Metadata("Unauthorized to modify dashboard"));
+        }
+    } else {
+        return Err(DashboardError::Metadata("Dashboard does not exist"));
+    }
+    
    dashboard.author = Some(user_id.clone());

    DASHBOARDS
        .update(&user_id, dashboard_id, &mut dashboard)
        .await?;

109-123: ⚠️ Potential issue

delete suffers from the same cross-tenant issue

Without verifying ownership, a malicious user can delete dashboards they don't own.

    let dashboard_id = if let Ok(dashboard_id) = Ulid::from_string(&dashboard_id.into_inner()) {
        dashboard_id
    } else {
        return Err(DashboardError::Metadata("Invalid dashboard ID"));
    };
+    
+    // Verify that the dashboard exists and belongs to the user
+    if let Some(existing) = DASHBOARDS.get_dashboard(dashboard_id).await {
+        if existing.author.as_ref().map_or(true, |author| author != &user_id) {
+            return Err(DashboardError::Metadata("Unauthorized to delete dashboard"));
+        }
+    } else {
+        return Err(DashboardError::Metadata("Dashboard does not exist"));
+    }
+    
    DASHBOARDS.delete_dashboard(&user_id, dashboard_id).await?;

34-60: ⚠️ Potential issue

Security issue: The list endpoint leaks dashboards across tenants

The list() function doesn't filter dashboards by the caller's identity, allowing any authenticated user to see all dashboards in the system regardless of ownership.

Implement authorization checks:

-pub async fn list() -> Result<impl Responder, DashboardError> {
-    let dashboards = DASHBOARDS.list_dashboards().await;
+pub async fn list(req: HttpRequest) -> Result<impl Responder, DashboardError> {
+    let mut user_id = get_user_from_request(&req)?;
+    user_id = get_hash(&user_id);
+    let dashboards = DASHBOARDS.list_dashboards().await
+        .into_iter()
+        .filter(|d| d.author.as_ref().map_or(false, |author| author == &user_id))
+        .collect::<Vec<_>>();

Also, the function unconditionally unwraps author and modified fields, which could cause runtime panics:

-            map.insert(
-                "author".to_string(),
-                serde_json::Value::String(dashboard.author.as_ref().unwrap().clone()),
-            );
+            if let Some(author) = &dashboard.author {
+                map.insert(
+                    "author".to_string(),
+                    serde_json::Value::String(author.clone()),
+                );
+            }

Apply similar changes for the modified and dashboard_id fields.


125-151: ⚠️ Potential issue

Security concern: New add_tile() function also lacks authorization checks

The newly added function allows users to modify any dashboard without verifying ownership.

    let dashboard_id = if let Ok(dashboard_id) = Ulid::from_string(&dashboard_id.into_inner()) {
        dashboard_id
    } else {
        return Err(DashboardError::Metadata("Invalid dashboard ID"));
    };

    let mut dashboard = DASHBOARDS
        .get_dashboard(dashboard_id)
        .await
        .ok_or(DashboardError::Metadata("Dashboard does not exist"))?;
+    
+    // Verify the dashboard belongs to the user
+    if dashboard.author.as_ref().map_or(true, |author| author != &user_id) {
+        return Err(DashboardError::Metadata("Unauthorized to modify dashboard"));
+    }
+    
    if tile.tile_id.is_nil() {
        tile.tile_id = Ulid::new();
    }

Also consider validating the tile data for required fields and consistent format before adding it to the dashboard.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd58a46 and 4f3d432.

📒 Files selected for processing (1)
  • src/handlers/http/users/dashboards.rs (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: Build Default x86_64-pc-windows-msvc
  • GitHub Check: Build Default aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-apple-darwin
  • GitHub Check: Build Default aarch64-unknown-linux-gnu
  • GitHub Check: Build Kafka aarch64-apple-darwin
  • GitHub Check: Quest Smoke and Load Tests for Standalone deployments
  • GitHub Check: Build Default x86_64-unknown-linux-gnu
  • GitHub Check: Build Kafka x86_64-unknown-linux-gnu
  • GitHub Check: coverage
  • GitHub Check: Quest Smoke and Load Tests for Distributed deployments

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/handlers/http/users/dashboards.rs (1)

34-59: ⚠️ Potential issue

Handle potential null values to prevent panics in list

The code unconditionally unwraps optional values, which could cause runtime panics:

-            map.insert(
-                "author".to_string(),
-                serde_json::Value::String(dashboard.author.as_ref().unwrap().clone()),
-            );
+            if let Some(author) = &dashboard.author {
+                map.insert(
+                    "author".to_string(),
+                    serde_json::Value::String(author.clone()),
+                );
+            }

Apply similar checks for modified and dashboard_id fields as well.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 886c1e0 and 9d1419c.

📒 Files selected for processing (1)
  • src/handlers/http/users/dashboards.rs (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/handlers/http/users/dashboards.rs (2)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.832Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:63-71
Timestamp: 2025-05-01T10:22:45.684Z
Learning: Dashboard design in Parseable follows a sharing model where any user can read (list/get) any dashboard, enabling dashboard sharing functionality, but only the dashboard author can modify (update/delete) their own dashboards.
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: Build Default x86_64-pc-windows-msvc
  • GitHub Check: Build Default aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-apple-darwin
  • GitHub Check: Build Kafka aarch64-apple-darwin
  • GitHub Check: Build Default aarch64-unknown-linux-gnu
  • GitHub Check: Build Kafka x86_64-unknown-linux-gnu
  • GitHub Check: Build Default x86_64-unknown-linux-gnu
  • GitHub Check: Quest Smoke and Load Tests for Standalone deployments
  • GitHub Check: coverage
  • GitHub Check: Quest Smoke and Load Tests for Distributed deployments
🔇 Additional comments (4)
src/handlers/http/users/dashboards.rs (4)

63-74: The dashboard access model allows reading by any user

This implementation correctly follows the sharing model described in the retrieved learnings, where any user can read any dashboard (enabling sharing functionality).

This approach allows dashboard sharing but puts the responsibility of proper authorization on the modify operations.


77-87: Dashboard creation logic looks good

The code correctly sets the author to the authenticated user and delegates dashboard creation to the centralized storage method.


89-107:

✅ Verification successful

Update function correctly handles dashboard ID validation

The function properly validates the dashboard ID format and delegates update responsibility to DASHBOARDS.update, which should handle ownership verification internally.


🏁 Script executed:

#!/bin/bash
# Check if DASHBOARDS.update verifies ownership
rg -A 10 "fn update.*user_id.*dashboard_id" src/users/dashboards.rs

Length of output: 69


To verify that DASHBOARDS.update enforces ownership, let’s first locate where DASHBOARDS is defined and inspect its update implementation:


🏁 Script executed:

#!/bin/bash
# Locate the definition of DASHBOARDS and the Dashboards struct
rg -n "DASHBOARDS" .
rg -n "struct Dashboards" .

Length of output: 1634


Let’s inspect the Dashboards implementation to confirm whether update enforces ownership:


🏁 Script executed:

#!/bin/bash
# Show the impl block and the update method in the storage layer
rg -A20 "impl Dashboards" src/users/dashboards.rs
rg -A20 "fn update" src/users/dashboards.rs

Length of output: 1711


Update handler and ownership enforcement are correct

The HTTP handler validates the dashboard ID format and delegates to DASHBOARDS.update, which internally calls get_dashboard_by_user to ensure the dashboard belongs to the current user before applying any changes. No further action is needed.


109-123:

✅ Verification successful

Delete function correctly validates dashboard ID

The function properly validates the dashboard ID format and delegates deletion to DASHBOARDS.delete_dashboard, which should handle ownership verification internally.


🏁 Script executed:

#!/bin/bash
# Check if DASHBOARDS.delete_dashboard verifies ownership
rg -A 10 "fn delete_dashboard.*user_id.*dashboard_id" src/users/dashboards.rs

Length of output: 79


I couldn’t find the delete_dashboard implementation in the handler file; let’s search the entire codebase to locate its definition and confirm ownership checks.


🏁 Script executed:

#!/bin/bash
# Search for the delete_dashboard function and the DASHBOARDS static
rg -n "fn delete_dashboard" .
rg -n "DASHBOARDS" .

Length of output: 1625


🏁 Script executed:

#!/bin/bash
# Extract implementation of delete_dashboard from business logic layer
rg -n -A 20 "pub async fn delete_dashboard" src/users/dashboards.rs

Length of output: 819


Ownership validation in delete_dashboard confirmed

The delete handler correctly parses and validates the dashboard ID, then calls DASHBOARDS.delete_dashboard(&user_id, dashboard_id). The implementation of delete_dashboard in src/users/dashboards.rs (lines 151–157) explicitly checks ownership via get_dashboard_by_user(...).await.is_none() and returns an error if the dashboard doesn’t belong to the user.

No further changes are needed here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/users/dashboards.rs (3)

146-169: Clippy warning: Use is_some_and instead of map_or

The code contains a pattern that can be simplified according to Clippy.

-        self.0.write().await.retain(|d| {
-            d.dashboard_id
-                .as_ref()
-                .map_or(false, |id| *id == dashboard_id)
-        });
+        self.0.write().await.retain(|d| {
+            d.dashboard_id
+                .as_ref()
+                .is_some_and(|id| *id == dashboard_id)
+        });
🧰 Tools
🪛 GitHub Actions: Lint, Test and Coverage Report

[error] 163-165: Clippy lint: this map_or can be simplified. Use is_some_and instead of .map_or(false, |id| *id == dashboard_id) (clippy::unnecessary_map_or).


171-182: Clippy warning: Use is_some_and instead of map_or

Similar to the previous comment, this code can be simplified.

-        self.0
-            .read()
-            .await
-            .iter()
-            .find(|d| {
-                d.dashboard_id
-                    .as_ref()
-                    .map_or(false, |id| *id == dashboard_id)
-            })
-            .cloned()
+        self.0
+            .read()
+            .await
+            .iter()
+            .find(|d| {
+                d.dashboard_id
+                    .as_ref()
+                    .is_some_and(|id| *id == dashboard_id)
+            })
+            .cloned()
🧰 Tools
🪛 GitHub Actions: Lint, Test and Coverage Report

[error] 177-179: Clippy lint: this map_or can be simplified. Use is_some_and instead of .map_or(false, |id| *id == dashboard_id) (clippy::unnecessary_map_or).


184-200: Clippy warning: Use is_some_and instead of map_or

The same pattern appears here and should be simplified according to Clippy.

-            .find(|d| {
-                d.dashboard_id
-                    .as_ref()
-                    .map_or(false, |id| *id == dashboard_id)
-                    && d.author == Some(user_id.to_string())
-            })
+            .find(|d| {
+                d.dashboard_id
+                    .as_ref()
+                    .is_some_and(|id| *id == dashboard_id)
+                    && d.author == Some(user_id.to_string())
+            })
🧰 Tools
🪛 GitHub Actions: Lint, Test and Coverage Report

[error] 194-196: Clippy lint: this map_or can be simplified. Use is_some_and instead of .map_or(false, |id| *id == dashboard_id) (clippy::unnecessary_map_or).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d1419c and 40fd7a1.

📒 Files selected for processing (2)
  • src/handlers/http/users/dashboards.rs (6 hunks)
  • src/users/dashboards.rs (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
src/handlers/http/users/dashboards.rs (3)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.832Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:63-71
Timestamp: 2025-05-01T10:22:45.684Z
Learning: Dashboard design in Parseable follows a sharing model where any user can read (list/get) any dashboard, enabling dashboard sharing functionality, but only the dashboard author can modify (update/delete) their own dashboards.
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:125-148
Timestamp: 2025-05-01T10:33:51.746Z
Learning: When adding a tile to a dashboard in `add_tile()` function, the tile ID must be provided by the client and should not be generated by the server. If the tile ID is missing (nil), the API should fail the operation with an appropriate error message.
src/users/dashboards.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.832Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
🪛 GitHub Actions: Lint, Test and Coverage Report
src/users/dashboards.rs

[error] 163-165: Clippy lint: this map_or can be simplified. Use is_some_and instead of .map_or(false, |id| *id == dashboard_id) (clippy::unnecessary_map_or).


[error] 177-179: Clippy lint: this map_or can be simplified. Use is_some_and instead of .map_or(false, |id| *id == dashboard_id) (clippy::unnecessary_map_or).


[error] 194-196: Clippy lint: this map_or can be simplified. Use is_some_and instead of .map_or(false, |id| *id == dashboard_id) (clippy::unnecessary_map_or).

⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: Build Default x86_64-pc-windows-msvc
  • GitHub Check: Build Default aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-apple-darwin
  • GitHub Check: Build Default aarch64-unknown-linux-gnu
  • GitHub Check: Build Kafka aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-unknown-linux-gnu
  • GitHub Check: Quest Smoke and Load Tests for Distributed deployments
  • GitHub Check: Build Kafka x86_64-unknown-linux-gnu
  • GitHub Check: Quest Smoke and Load Tests for Standalone deployments
🔇 Additional comments (12)
src/handlers/http/users/dashboards.rs (7)

33-65: Dashboard listing correctly implements the sharing model

The list method follows the dashboard sharing model design where any user can read any dashboard. The method safely handles optional fields with appropriate null checks for author, modified, and dashboard_id, preventing potential runtime panics.


68-75: Get endpoint properly validates dashboard ID

The get function correctly uses the new validate_dashboard_id helper, improving error handling for invalid IDs. Following the sharing model, this endpoint allows any user to view any dashboard.


82-84: Good title validation in the POST endpoint

The title validation check prevents creation of dashboards with empty titles, improving data quality and user experience.


102-108: Well-implemented tile ID validation

The function correctly validates that all tiles have a non-nil tile_id, aligning with the requirement that tile IDs must be provided by the client and not generated by the server.


123-127: Delete endpoint properly delegates authorization check

The delete function correctly validates the dashboard ID and delegates the deletion to DASHBOARDS.delete_dashboard, which ensures that only the dashboard author can delete the dashboard.


169-182: Good addition of Unauthorized error type

Adding a dedicated Unauthorized error variant with appropriate HTTP status code (401) improves the API's error handling for authorization failures. This makes the authorization errors more descriptive and sets the appropriate status code.


129-155:

✅ Verification successful

Well-implemented add_tile endpoint with proper authorization

The add_tile function correctly:

  1. Validates the dashboard ID
  2. Checks that the tile ID is not nil (client must provide it)
  3. Uses get_dashboard_by_user to ensure only the dashboard author can add tiles
  4. Safely handles the optional tiles vector with get_or_insert_with

This implementation aligns with the requirement that the function should use get_dashboard_by_user instead of get_dashboard to ensure proper authorization.


🏁 Script executed:

#!/bin/bash
# Verify the implementation of add_tile respects authorization and requires tile ID

# Check if we're using get_dashboard_by_user for proper authorization
rg -A 5 "get_dashboard_by_user" src/handlers/http/users/dashboards.rs

# Check if we're validating tile ID is present
rg "tile_id.is_nil" src/handlers/http/users/dashboards.rs

Length of output: 417


add_tile endpoint correctly enforces authorization and validation

All required checks are present and correctly implemented:

  • Dashboard ID is validated via validate_dashboard_id
  • Tile ID presence is enforced with tile.tile_id.is_nil()
  • Authorization is enforced by calling
    DASHBOARDS.get_dashboard_by_user(dashboard_id, &user_id)
  • Existing tiles vector is safely handled with get_or_insert_with

No further changes needed—approving as-is.

src/users/dashboards.rs (5)

42-47: Dashboard tile structure simplified appropriately

The new Tile struct is well-designed with a required tile_id field and an optional map for additional fields, providing flexibility while maintaining the required identifier.


48-57: Dashboard structure streamlined with proper optional fields

The Dashboard struct has been appropriately streamlined:

  • Required fields: title
  • Optional fields: version, author, dashboard_id, modified, dashboard_type, tiles

This design provides flexibility while maintaining the core requirements.


110-144: Good authorization check in update method

The update method correctly verifies that the user is the author of the dashboard before allowing updates, using get_dashboard_by_user for authorization.


202-211: List dashboards implementation aligns with sharing model

The list_dashboards method returns all dashboards without filtering by user, which aligns with the dashboard sharing model where any user can read any dashboard.


214-216: Good utility function for dashboard ID validation

The validate_dashboard_id function provides a centralized way to parse and validate dashboard IDs, improving consistency and error handling throughout the codebase.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/users/dashboards.rs (3)

208-212: Simplify map_or with is_none_or

The pipeline is suggesting to simplify this code with Rust's newer is_none_or method.

-        self.0.write().await.retain(|d| {
-            d.dashboard_id
-                .as_ref()
-                .map_or(true, |id| *id != dashboard_id)
-        });
+        self.0.write().await.retain(|d| {
+            d.dashboard_id
+                .as_ref()
+                .is_none_or(|id| *id != dashboard_id)
+        });
🧰 Tools
🪛 GitHub Actions: Lint, Test and Coverage Report

[error] 209-211: Clippy lint: this map_or can be simplified. Suggestion: use is_none_or instead of .map_or(true, |id| *id != dashboard_id).


222-226: Simplify map_or with is_some_and

The pipeline is suggesting to simplify this code with Rust's newer is_some_and method.

-            .find(|d| {
-                d.dashboard_id
-                    .as_ref()
-                    .map_or(false, |id| *id == dashboard_id)
-            })
+            .find(|d| {
+                d.dashboard_id
+                    .as_ref()
+                    .is_some_and(|id| *id == dashboard_id)
+            })
🧰 Tools
🪛 GitHub Actions: Lint, Test and Coverage Report

[error] 223-225: Clippy lint: this map_or can be simplified. Suggestion: use is_some_and instead of .map_or(false, |id| *id == dashboard_id).


239-244: Simplify map_or with is_some_and

The pipeline is suggesting to simplify this code with Rust's newer is_some_and method.

-            .find(|d| {
-                d.dashboard_id
-                    .as_ref()
-                    .map_or(false, |id| *id == dashboard_id)
-                    && d.author == Some(user_id.to_string())
-            })
+            .find(|d| {
+                d.dashboard_id
+                    .as_ref()
+                    .is_some_and(|id| *id == dashboard_id)
+                    && d.author == Some(user_id.to_string())
+            })
🧰 Tools
🪛 GitHub Actions: Lint, Test and Coverage Report

[error] 240-242: Clippy lint: this map_or can be simplified. Suggestion: use is_some_and instead of .map_or(false, |id| *id == dashboard_id).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40fd7a1 and 9a2d5f5.

📒 Files selected for processing (3)
  • src/handlers/http/modal/server.rs (1 hunks)
  • src/handlers/http/users/dashboards.rs (3 hunks)
  • src/users/dashboards.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/handlers/http/users/dashboards.rs
  • src/handlers/http/modal/server.rs
🧰 Additional context used
🧠 Learnings (1)
src/users/dashboards.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.832Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
🪛 GitHub Actions: Lint, Test and Coverage Report
src/users/dashboards.rs

[error] 209-211: Clippy lint: this map_or can be simplified. Suggestion: use is_none_or instead of .map_or(true, |id| *id != dashboard_id).


[error] 223-225: Clippy lint: this map_or can be simplified. Suggestion: use is_some_and instead of .map_or(false, |id| *id == dashboard_id).


[error] 240-242: Clippy lint: this map_or can be simplified. Suggestion: use is_some_and instead of .map_or(false, |id| *id == dashboard_id).

⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: Build Default x86_64-pc-windows-msvc
  • GitHub Check: Build Default aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-apple-darwin
  • GitHub Check: Build Kafka aarch64-apple-darwin
  • GitHub Check: Build Default aarch64-unknown-linux-gnu
  • GitHub Check: Build Default x86_64-unknown-linux-gnu
  • GitHub Check: Quest Smoke and Load Tests for Distributed deployments
  • GitHub Check: Build Kafka x86_64-unknown-linux-gnu
  • GitHub Check: Quest Smoke and Load Tests for Standalone deployments
🔇 Additional comments (11)
src/users/dashboards.rs (11)

33-33: Version reset: CURRENT_DASHBOARD_VERSION changed from "v3" to "v1"

The version constant has been reset from "v3" to "v1", indicating a significant simplification of the dashboard system and removal of version migration logic. This aligns with the overall refactoring to flatten the dashboard data model.


35-40: New DashboardType enum provides better categorization

Adding the DashboardType enum with Dashboard and Report variants creates clearer semantics for different dashboard use cases.


42-47: Simplified Tile structure with ULID identifiers

The Tile struct has been greatly simplified to use Ulid for identifiers and a flattened map for additional fields. This addresses previous friction between ID types and provides more flexibility.


48-57: Dashboard struct now uses optional fields

The Dashboard struct has been refactored to use optional fields for dashboard_id and tiles. While this provides flexibility, it introduces potential null reference issues if not carefully handled.

Make sure these optional fields are properly initialized and checked before use throughout the codebase.


59-66: Good addition of set_metadata method

The set_metadata method provides consistent initialization of dashboard metadata, addressing the previous concern about defensive initialization in the Dashboard struct.


101-104: ULID validation function provides better error handling

The validate_dashboard_id function centralizes the validation logic for dashboard IDs, making error handling more consistent throughout the codebase.


154-165: Create method now properly initializes metadata

The create method now uses the set_metadata helper to ensure consistent dashboard initialization before saving to storage.


167-179: Authorization check added to update method

The update method now properly checks if the dashboard exists for the specified user using get_dashboard_by_user and returns DashboardError::Unauthorized if not found. This addresses a previous security concern about missing authorization checks.


191-202: Authorization check added to delete_dashboard method

Similar to the update method, the delete_dashboard method now verifies user authorization before allowing deletion. This is a good security improvement that protects against unauthorized dashboard modifications.


248-250: List dashboards returns all dashboards without user filtering

The list_dashboards method now returns all dashboards without filtering by user. This simplifies the API but might expose dashboards across users if access control isn't implemented at the API layer.

Consider adding an optional user filtering parameter if you need to maintain isolation between different users' dashboards.


230-246: Added get_dashboard_by_user method for authorization checks

The new get_dashboard_by_user method properly checks both dashboard ID and user ID, providing the foundation for proper authorization throughout the codebase. This correctly implements the learning from previous PR feedback.

🧰 Tools
🪛 GitHub Actions: Lint, Test and Coverage Report

[error] 240-242: Clippy lint: this map_or can be simplified. Suggestion: use is_some_and instead of .map_or(false, |id| *id == dashboard_id).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/users/dashboards.rs (2)

191-202: Apply the same ownership check improvement to delete_dashboard.

The delete_dashboard method has the same authorization check pattern as the update method and should use the same centralized function.

 pub async fn delete_dashboard(
     &self,
     user_id: &str,
     dashboard_id: Ulid,
 ) -> Result<(), DashboardError> {
-    if self
-        .get_dashboard_by_user(dashboard_id, user_id)
-        .await
-        .is_none()
-    {
-        return Err(DashboardError::Unauthorized);
-    }
+    // Verify ownership
+    let _ = self.ensure_dashboard_ownership(dashboard_id, user_id).await?;

49-57: 🛠️ Refactor suggestion

Implement constructor method for Dashboard struct.

The Dashboard struct has several optional fields but doesn't provide a constructor method to ensure they're properly initialized, which could lead to the unwrap issues identified in previous reviews.

 #[derive(Debug, Serialize, Deserialize, Default, Clone)]
 pub struct Dashboard {
     pub version: Option<String>,
     pub title: String,
     pub author: Option<String>,
     pub dashboard_id: Option<Ulid>,
     pub modified: Option<DateTime<Utc>>,
     dashboard_type: Option<DashboardType>,
     pub tiles: Option<Vec<Tile>>,
+}
+
+impl Dashboard {
+    pub fn new(title: String) -> Self {
+        Self {
+            version: Some(CURRENT_DASHBOARD_VERSION.to_string()),
+            title,
+            author: None,
+            dashboard_id: Some(Ulid::new()),
+            modified: Some(Utc::now()),
+            dashboard_type: Some(DashboardType::Dashboard),
+            tiles: Some(Vec::new()),
+        }
+    }
 }
🧹 Nitpick comments (8)
src/users/dashboards.rs (8)

35-40: Add documentation for the DashboardType enum.

The enum is properly defined with a default value, but it lacks documentation explaining the difference between a "Dashboard" and a "Report" type.

 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
+/// Represents the type of dashboard visualization.
 pub enum DashboardType {
+    /// Standard interactive dashboard with multiple tiles.
     #[default]
     Dashboard,
+    /// Static report format, typically for scheduled delivery or exports.
     Report,
 }

43-47: Implement constructor method for Tile struct.

The Tile struct has a required tile_id field but no constructor method to ensure it's properly initialized.

 #[derive(Debug, Serialize, Deserialize, Default, Clone)]
 pub struct Tile {
     pub tile_id: Ulid,
     #[serde(flatten)]
     pub other_fields: Option<serde_json::Map<String, Value>>,
+}
+
+impl Tile {
+    pub fn new(tile_id: Option<Ulid>) -> Self {
+        Self {
+            tile_id: tile_id.unwrap_or_else(Ulid::new),
+            other_fields: None,
+        }
+    }
+
+    pub fn with_fields(mut self, fields: serde_json::Map<String, Value>) -> Self {
+        self.other_fields = Some(fields);
+        self
+    }
 }

101-104: Improve error message in validate_dashboard_id.

The error message "Dashboard ID must be provided" when Ulid::from_string fails is misleading, as it suggests the ID is missing rather than invalid.

 pub fn validate_dashboard_id(dashboard_id: String) -> Result<Ulid, DashboardError> {
     Ulid::from_string(&dashboard_id)
-        .map_err(|_| DashboardError::Metadata("Dashboard ID must be provided"))
+        .map_err(|_| DashboardError::Metadata("Invalid dashboard ID format - must be a valid ULID"))
 }

120-121: Handle malformed JSON with better error messages.

The current implementation silently discards dashboards that fail to parse without logging the error or the offending dashboard.

 if dashboard.is_empty() {
     continue;
 }

-let dashboard_value = serde_json::from_slice::<serde_json::Value>(&dashboard)?;
-if let Ok(dashboard) = serde_json::from_value::<Dashboard>(dashboard_value) {
+let dashboard_value = match serde_json::from_slice::<serde_json::Value>(&dashboard) {
+    Ok(value) => value,
+    Err(err) => {
+        tracing::warn!("Failed to parse dashboard JSON: {}", err);
+        continue;
+    }
+};
+
+if let Ok(dashboard) = serde_json::from_value::<Dashboard>(dashboard_value.clone()) {
     this.retain(|d: &Dashboard| d.dashboard_id != dashboard.dashboard_id);
     this.push(dashboard);
+} else {
+    tracing::warn!("Failed to deserialize dashboard: {:?}", dashboard_value);
 }

167-179: Use centralized authorization check in update method.

The update method uses a specific conditional to check dashboard ownership. This authentication check should be extracted into a reusable function for consistency with other methods.

+async fn ensure_dashboard_ownership(
+    &self,
+    dashboard_id: Ulid,
+    user_id: &str,
+) -> Result<Dashboard, DashboardError> {
+    self.get_dashboard_by_user(dashboard_id, user_id)
+        .await
+        .ok_or(DashboardError::Unauthorized)
+}
+
 pub async fn update(
     &self,
     user_id: &str,
     dashboard_id: Ulid,
     dashboard: &mut Dashboard,
 ) -> Result<(), DashboardError> {
-    if self
-        .get_dashboard_by_user(dashboard_id, user_id)
-        .await
-        .is_none()
-    {
-        return Err(DashboardError::Unauthorized);
-    }
+    // Verify ownership
+    let _ = self.ensure_dashboard_ownership(dashboard_id, user_id).await?;

208-212: Simplify dashboard retention logic.

The dashboard retention logic is awkwardly phrased with double negation. This could be simplified for better readability.

-    self.0.write().await.retain(|d| {
-        d.dashboard_id
-            .as_ref()
-            .is_some_and(|id| *id == dashboard_id)
-    });
+    self.0.write().await.retain(|d| {
+        !d.dashboard_id
+            .as_ref()
+            .is_some_and(|id| *id == dashboard_id)
+    });

Or even better, for positive logic:

-    self.0.write().await.retain(|d| {
-        d.dashboard_id
-            .as_ref()
-            .is_some_and(|id| *id == dashboard_id)
-    });
+    self.0.write().await.retain(|d| {
+        d.dashboard_id
+            .as_ref()
+            .map_or(true, |id| *id != dashboard_id)
+    });

248-250: Consider adding pagination to list_dashboards.

The list_dashboards method simply returns a clone of all dashboards with no filtering or pagination, which could be inefficient for large numbers of dashboards.

-pub async fn list_dashboards(&self) -> Vec<Dashboard> {
-    self.0.read().await.clone()
+pub async fn list_dashboards(
+    &self,
+    offset: Option<usize>,
+    limit: Option<usize>,
+) -> Vec<Dashboard> {
+    let dashboards = self.0.read().await;
+    let offset = offset.unwrap_or(0);
+    let limit = limit.unwrap_or(dashboards.len());
+    
+    dashboards
+        .iter()
+        .skip(offset)
+        .take(limit)
+        .cloned()
+        .collect()
 }

230-246: Add missing functionality for adding tiles to a dashboard.

Based on the retrieved learning, there should be an add_tile function in the HTTP handler that uses get_dashboard_by_user. However, there's no corresponding method in the Dashboards struct to support this operation.

+pub async fn add_tile(
+    &self,
+    user_id: &str,
+    dashboard_id: Ulid,
+    tile: Tile,
+) -> Result<(), DashboardError> {
+    let mut dashboard = self.ensure_dashboard_ownership(dashboard_id, user_id).await?;
+    
+    // Initialize tiles vector if it doesn't exist
+    if dashboard.tiles.is_none() {
+        dashboard.tiles = Some(Vec::new());
+    }
+    
+    // Check for duplicate tile_id
+    if let Some(tiles) = &dashboard.tiles {
+        if tiles.iter().any(|t| t.tile_id == tile.tile_id) {
+            return Err(DashboardError::Metadata("Tile with this ID already exists"));
+        }
+    }
+    
+    // Add the new tile
+    if let Some(tiles) = &mut dashboard.tiles {
+        tiles.push(tile);
+    }
+    
+    // Update modification timestamp
+    dashboard.modified = Some(Utc::now());
+    
+    // Save the updated dashboard
+    self.update(user_id, dashboard_id, &mut dashboard).await
+}

Would you like me to add this implementation to support the add_tile functionality mentioned in the retrieved learning?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a2d5f5 and cb9d135.

📒 Files selected for processing (1)
  • src/users/dashboards.rs (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/users/dashboards.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.832Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: Build Default x86_64-pc-windows-msvc
  • GitHub Check: Build Default aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-apple-darwin
  • GitHub Check: Build Default aarch64-unknown-linux-gnu
  • GitHub Check: Build Kafka aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-unknown-linux-gnu
  • GitHub Check: Quest Smoke and Load Tests for Distributed deployments
  • GitHub Check: Quest Smoke and Load Tests for Standalone deployments
  • GitHub Check: coverage
  • GitHub Check: Build Kafka x86_64-unknown-linux-gnu
🔇 Additional comments (2)
src/users/dashboards.rs (2)

25-25: Good addition of the ulid dependency for standardized IDs.

Using ULIDs for dashboard and tile identifiers is a strong improvement over custom string hashes, as it provides:

  • Global uniqueness guarantee
  • Natural ordering for sorting
  • Millisecond timestamp precision for easy chronological ordering

33-33: Confirm version reset from "v3" to "v1" is intentional.

The dashboard version has been reset from a previous "v3" to "v1". This suggests a breaking change in the dashboard data model that removes backward compatibility with older dashboard structures.

Please verify that this change is intentional and that any existing dashboards will be properly migrated or handled.

coderabbitai[bot]
coderabbitai bot previously approved these changes May 3, 2025
coderabbitai[bot]
coderabbitai bot previously approved these changes May 3, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/prism/home/mod.rs (1)

369-392: ⚠️ Potential issue

Dashboard listing still ignores RBAC – leaks titles across tenants

This helper fetches all dashboards (DASHBOARDS.list_dashboards()) and filters only by the query text, not by ownership or visibility.
The same issue was flagged in a previous review and remains unresolved; any authenticated user can enumerate every dashboard title/ID in the cluster.

Please enforce authorisation, e.g. by filtering on dashboard.author == key.user_id or moving the check into the data layer.

🧹 Nitpick comments (3)
src/handlers/http/prism_home.rs (1)

45-64: Avoid the extra serde_json::to_value hop and clarify variable naming

generate_home_search_response already returns a HomeSearchResponse that implements Serialize, so you can hand it straight to web::Json.
Serialising it into a serde_json::Value first creates an unnecessary allocation and copies the whole structure once more.

At the same time, the query parameter you expect is literally called "key", while the local variable key holds the session key. The over-loaded wording is easy to mis-read in reviews and logs.

-    let res = generate_home_search_response(&key, &query_value).await?;
-    let json_res = serde_json::to_value(res)
-        .map_err(|err| PrismHomeError::Anyhow(anyhow::Error::msg(err.to_string())))?;
-
-    Ok(web::Json(json_res))
+    let res = generate_home_search_response(&key, &query_value).await?;
+    Ok(web::Json(res))

If you want to keep the constant unchanged, consider renaming the local variable to session_key to avoid the mental clash.

src/prism/home/mod.rs (2)

141-147: Large HashMap cloned per-day – switch to a shared reference to save memory

stream_wise_meta.clone() is executed once for every day (7× by default).
When the map holds thousands of streams ‑ each with ObjectStoreFormat blobs ‑ this multiplies memory pressure and CPU time unnecessarily.

Passing &stream_wise_meta (or wrapping the value in an Arc/Rc) lets all async tasks read the same data without replication.


286-294: Minor allocation hotspot in dataset search

title.to_lowercase() allocates a fresh String for every dataset on every search.
For a large number of streams, that’s avoidable churn.

You can either

  • call unicode_segmentation::UnicodeSegmentation::graphemes etc. for case-insensitive checks without allocation, or
  • cache title.to_ascii_lowercase() once per iteration:
let lower_title = title.to_ascii_lowercase();
if lower_title.contains(query_value) {
    ...
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ea935b and 57587f6.

📒 Files selected for processing (3)
  • src/handlers/http/modal/server.rs (2 hunks)
  • src/handlers/http/prism_home.rs (2 hunks)
  • src/prism/home/mod.rs (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/handlers/http/modal/server.rs
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/prism/home/mod.rs (1)
src/alerts/mod.rs (4)
  • get_alerts_info (982-1019)
  • new (114-120)
  • new (155-167)
  • new (178-184)

Comment on lines 103 to 108
let mut dates = (0..7)
.map(|i| {
Utc::now()
.checked_sub_signed(chrono::Duration::days(i))
.ok_or_else(|| anyhow::Error::msg("Date conversion failed"))
.unwrap()
.expect("Date conversion failed")
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Production code should not expect() – bubble the error instead

checked_sub_signed can fail on very large offsets or if the system clock is skewed.
Calling .expect("Date conversion failed") will panic the whole HTTP worker thread and drop in-flight requests.

-            Utc::now()
-                .checked_sub_signed(chrono::Duration::days(i))
-                .expect("Date conversion failed")
+            Utc::now()
+                .checked_sub_signed(chrono::Duration::days(i))
+                .ok_or_else(|| PrismHomeError::Anyhow(anyhow::anyhow!("Failed to compute date")) )?

Returning a proper PrismHomeError keeps the service alive and surfaces the root cause to the caller.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let mut dates = (0..7)
.map(|i| {
Utc::now()
.checked_sub_signed(chrono::Duration::days(i))
.ok_or_else(|| anyhow::Error::msg("Date conversion failed"))
.unwrap()
.expect("Date conversion failed")
})
let mut dates = (0..7)
.map(|i| {
Utc::now()
.checked_sub_signed(chrono::Duration::days(i))
.ok_or_else(|| PrismHomeError::Anyhow(anyhow::anyhow!("Failed to compute date")) )?
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant