adding stats cards to devices page #2698

Merged
mfreeman451 merged 2 commits from refs/pull/2698/head into staging 2026-01-18 19:36:58 +00:00
mfreeman451 commented 2026-01-18 17:35:31 +00:00 (Migrated from github.com)
Owner

Imported from GitHub pull request.

Original GitHub pull request: #2352
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/2352
Original created: 2026-01-18T17:35:31Z
Original updated: 2026-01-18T19:36:59Z
Original head: carverauto/serviceradar:2252-feat-stats-cards-on-devices-ui-dashboard
Original base: staging
Original merged: 2026-01-18T19:36:58Z by @mfreeman451

User description

IMPORTANT: Please sign the Developer Certificate of Origin

Thank you for your contribution to ServiceRadar. Please note, when contributing, the developer must include
a DCO sign-off statement indicating the DCO acceptance in one commit message. Here
is an example DCO Signed-off-by line in a commit message:

Signed-off-by: J. Doe <j.doe@domain.com>

Describe your changes

Code checklist before requesting a review

  • I have signed the DCO?
  • The build completes without errors?
  • All tests are passing when running make test?

PR Type

Enhancement


Description

  • Add SRQL GROUP BY support for device stats queries (type, vendor_name, risk_level, is_available, gateway_id)

  • Implement device stats cards component displaying total, available, unavailable, and distribution metrics

  • Load stats asynchronously via parallel SRQL queries with skeleton loading state

  • Make stats cards clickable to filter devices table by availability and other dimensions


Diagram Walkthrough

flowchart LR
  Parser["SRQL Parser<br/>Handle 'by field' syntax"]
  DeviceRS["devices.rs<br/>Build grouped stats SQL"]
  Catalog["SRQL Catalog<br/>Define stats_fields"]
  LiveView["Device LiveView<br/>Load stats async"]
  Component["Stats Cards<br/>Display & filter"]
  
  Parser -->|Parse GROUP BY| DeviceRS
  DeviceRS -->|Execute queries| Catalog
  Catalog -->|Query via NIF| LiveView
  LiveView -->|Render component| Component

File Walkthrough

Relevant files
Enhancement
3 files
parser.rs
Add GROUP BY clause parsing to stats syntax                           
+35/-0   
devices.rs
Implement grouped stats queries with raw SQL                         
+384/-5 
index.ex
Add stats cards component and async loading                           
+301/-2 
Tests
1 files
mod.rs
Add tests for devices GROUP BY queries                                     
+92/-0   
Configuration changes
1 files
catalog.ex
Define stats_fields for device grouping                                   
+2/-0     
Documentation
5 files
proposal.md
Document feature proposal and scope                                           
+30/-0   
design.md
Detail implementation decisions and patterns                         
+181/-0 
spec.md
Define device dashboard stats requirements                             
+40/-0   
spec.md
Define SRQL GROUP BY query requirements                                   
+51/-0   
tasks.md
Track implementation tasks and checklist                                 
+46/-0   

Imported from GitHub pull request. Original GitHub pull request: #2352 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/2352 Original created: 2026-01-18T17:35:31Z Original updated: 2026-01-18T19:36:59Z Original head: carverauto/serviceradar:2252-feat-stats-cards-on-devices-ui-dashboard Original base: staging Original merged: 2026-01-18T19:36:58Z by @mfreeman451 --- ### **User description** ## IMPORTANT: Please sign the Developer Certificate of Origin Thank you for your contribution to ServiceRadar. Please note, when contributing, the developer must include a [DCO sign-off statement]( https://developercertificate.org/) indicating the DCO acceptance in one commit message. Here is an example DCO Signed-off-by line in a commit message: ``` Signed-off-by: J. Doe <j.doe@domain.com> ``` ## Describe your changes ## Issue ticket number and link ## Code checklist before requesting a review - [ ] I have signed the DCO? - [ ] The build completes without errors? - [ ] All tests are passing when running make test? ___ ### **PR Type** Enhancement ___ ### **Description** - Add SRQL GROUP BY support for device stats queries (type, vendor_name, risk_level, is_available, gateway_id) - Implement device stats cards component displaying total, available, unavailable, and distribution metrics - Load stats asynchronously via parallel SRQL queries with skeleton loading state - Make stats cards clickable to filter devices table by availability and other dimensions ___ ### Diagram Walkthrough ```mermaid flowchart LR Parser["SRQL Parser<br/>Handle 'by field' syntax"] DeviceRS["devices.rs<br/>Build grouped stats SQL"] Catalog["SRQL Catalog<br/>Define stats_fields"] LiveView["Device LiveView<br/>Load stats async"] Component["Stats Cards<br/>Display & filter"] Parser -->|Parse GROUP BY| DeviceRS DeviceRS -->|Execute queries| Catalog Catalog -->|Query via NIF| LiveView LiveView -->|Render component| Component ``` <details><summary><h3>File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>3 files</summary><table> <tr> <td><strong>parser.rs</strong><dd><code>Add GROUP BY clause parsing to stats syntax</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-b2edf55d1721185349ecddb2f4eacc42e0dfcae19b6c2bc638602f187da67e66">+35/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>devices.rs</strong><dd><code>Implement grouped stats queries with raw SQL</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-3202f22fff6863ed7848a129c49e2323322462b379d896d3fca2e59aa6f7b4c5">+384/-5</a>&nbsp; </td> </tr> <tr> <td><strong>index.ex</strong><dd><code>Add stats cards component and async loading</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-261a01f4876e5984e1d9e9b38a3540675dfb0272abc30e6bdb2a4fa610353cc7">+301/-2</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>mod.rs</strong><dd><code>Add tests for devices GROUP BY queries</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-393e3fa3d0e41741834cd7cd398a06111ab7b141ae6caca7a5dcc0e036172491">+92/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>catalog.ex</strong><dd><code>Define stats_fields for device grouping</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-80c07c25c17e48bf86860ec91db10256b7700a863d5371798e1893e741dc0e15">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>5 files</summary><table> <tr> <td><strong>proposal.md</strong><dd><code>Document feature proposal and scope</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-fcf6422f2f4757f42bed94091e829e40f000a45e471408b95cfffdd95a33c371">+30/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>design.md</strong><dd><code>Detail implementation decisions and patterns</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-155ffcba16864987c78fe6550405a60a8cbb7711b64019777c006e575e92484b">+181/-0</a>&nbsp; </td> </tr> <tr> <td><strong>spec.md</strong><dd><code>Define device dashboard stats requirements</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-5e129a20a595879416ca99e82ef323a4fd8acac347d23f9debab246eae446e59">+40/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong><dd><code>Define SRQL GROUP BY query requirements</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-57d87294b315fcff9ecd45bc2b643a0dd7e898a4520364f7d3dbe5ea5dbff9dd">+51/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>tasks.md</strong><dd><code>Track implementation tasks and checklist</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-7ab7b48288c3779b58840375cfd7ab35eaf0a8b82ba61d86197143e8b5e96b2f">+46/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr></tbody></table> </details> ___
qodo-code-review[bot] commented 2026-01-18 17:36:10 +00:00 (Migrated from github.com)
Author
Owner

Imported GitHub PR comment.

Original author: @qodo-code-review[bot]
Original URL: https://github.com/carverauto/serviceradar/pull/2352#issuecomment-3765530224
Original created: 2026-01-18T17:36:10Z

ⓘ Your approaching your monthly quota for Qodo. Upgrade your plan

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🟡
🎫 #2252
🟢 Display stats cards above the devices table in the devices dashboard UI.
Show device inventory aggregates including total count, online/offline
(available/unavailable), and breakdown by vendor (and similar breakdowns such as
type/risk).
🔴 Ensure the source of aggregates comes from CAGGs in CNPG for performance.
Use Ash migrations (with core-elx kicking off migrations) to support any needed DB
changes.
Confirm the stats queries are performant at expected production scale (since the
implementation appears to query ocsf_devices directly rather than querying CAGGs).
Verify the UI behavior matches expectations for “async” loading (the code runs parallel
queries but appears to wait for completion before rendering non-loading state).
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Silent stats failure: load_device_stats/2 rescues all exceptions and returns zero/empty stats without logging or
exposing actionable context, making failures invisible and hard to debug.

Referred Code
defp load_device_stats(srql_module, scope) do
  queries = %{
    total: ~s|in:devices stats:"count() as total"|,
    available: ~s|in:devices is_available:true stats:"count() as count"|,
    unavailable: ~s|in:devices is_available:false stats:"count() as count"|,
    by_type: ~s|in:devices stats:count() as count by type|,
    by_vendor: ~s|in:devices stats:count() as count by vendor_name|,
    by_risk_level: ~s|in:devices stats:count() as count by risk_level|
  }

  results =
    queries
    |> Task.async_stream(
      fn {key, query} -> {key, srql_module.query(query, %{scope: scope})} end,
      ordered: false,
      timeout: 10_000
    )
    |> Enum.reduce(%{}, fn
      {:ok, {key, {:ok, result}}}, acc -> Map.put(acc, key, result)
      {:ok, {key, result}}, acc -> Map.put(acc, key, result)
      _, acc -> acc


 ... (clipped 21 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
- Requires Further Human Verification
🏷️ - Compliance label
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2352#issuecomment-3765530224 Original created: 2026-01-18T17:36:10Z --- <pre>ⓘ Your approaching your monthly quota for Qodo. <a href="https://www.qodo.ai/pricing">Upgrade your plan</a></pre> ## PR Compliance Guide 🔍 <!-- https://github.com/carverauto/serviceradar/commit/b95482c410bc9c835dfe8c0242ed84ada49e8cda --> Below is a summary of compliance checks for this PR:<br> <table><tbody><tr><td colspan='2'><strong>Security Compliance</strong></td></tr> <tr><td>🟢</td><td><details><summary><strong>No security concerns identified</strong></summary> No security vulnerabilities detected by AI analysis. Human verification advised for critical code. </details></td></tr> <tr><td colspan='2'><strong>Ticket Compliance</strong></td></tr> <tr><td>🟡</td> <td> <details> <summary>🎫 <a href=https://github.com/carverauto/serviceradar/issues/2252>#2252</a></summary> <table width='100%'><tbody> <tr><td rowspan=2>🟢</td> <td>Display stats cards above the devices table in the devices dashboard UI.</td></tr> <tr><td>Show device inventory aggregates including total count, online/offline <br>(available/unavailable), and breakdown by vendor (and similar breakdowns such as <br>type/risk).</td></tr> <tr><td rowspan=2>🔴</td> <td>Ensure the source of aggregates comes from CAGGs in CNPG for performance.</td></tr> <tr><td>Use Ash migrations (with <code>core-elx</code> kicking off migrations) to support any needed DB <br>changes.</td></tr> <tr><td rowspan=2>⚪</td> <td>Confirm the stats queries are performant at expected production scale (since the <br>implementation appears to query <code>ocsf_devices</code> directly rather than querying CAGGs).</td></tr> <tr><td>Verify the UI behavior matches expectations for “async” loading (the code runs parallel <br>queries but appears to wait for completion before rendering non-loading state).</td></tr> </tbody></table> </details> </td></tr> <tr><td colspan='2'><strong>Codebase Duplication Compliance</strong></td></tr> <tr><td>⚪</td><td><details><summary><strong>Codebase context is not defined </strong></summary> Follow the <a href='https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/'>guide</a> to enable codebase context checks. </details></td></tr> <tr><td colspan='2'><strong>Custom Compliance</strong></td></tr> <tr><td rowspan=5>🟢</td><td> <details><summary><strong>Generic: Comprehensive Audit Trails</strong></summary><br> **Objective:** To create a detailed and reliable record of critical system actions for security analysis <br>and compliance.<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Meaningful Naming and Self-Documenting Code</strong></summary><br> **Objective:** Ensure all identifiers clearly express their purpose and intent, making code <br>self-documenting<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Secure Error Handling</strong></summary><br> **Objective:** To prevent the leakage of sensitive system information through error messages while <br>providing sufficient detail for internal debugging.<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Secure Logging Practices</strong></summary><br> **Objective:** To ensure logs are useful for debugging and auditing without exposing sensitive <br>information like PII, PHI, or cardholder data.<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Security-First Input Validation and Data Handling</strong></summary><br> **Objective:** Ensure all data inputs are validated, sanitized, and handled securely to prevent <br>vulnerabilities<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td rowspan=1>🔴</td> <td><details> <summary><strong>Generic: Robust Error Handling and Edge Case Management</strong></summary><br> **Objective:** Ensure comprehensive error handling that provides meaningful context and graceful <br>degradation<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/2352/files#diff-261a01f4876e5984e1d9e9b38a3540675dfb0272abc30e6bdb2a4fa610353cc7R1936-R1977'><strong>Silent stats failure</strong></a>: <code>load_device_stats/2</code> rescues all exceptions and returns zero/empty stats without logging or <br>exposing actionable context, making failures invisible and hard to debug.<br> <details open><summary>Referred Code</summary> ```elixir defp load_device_stats(srql_module, scope) do queries = %{ total: ~s|in:devices stats:"count() as total"|, available: ~s|in:devices is_available:true stats:"count() as count"|, unavailable: ~s|in:devices is_available:false stats:"count() as count"|, by_type: ~s|in:devices stats:count() as count by type|, by_vendor: ~s|in:devices stats:count() as count by vendor_name|, by_risk_level: ~s|in:devices stats:count() as count by risk_level| } results = queries |> Task.async_stream( fn {key, query} -> {key, srql_module.query(query, %{scope: scope})} end, ordered: false, timeout: 10_000 ) |> Enum.reduce(%{}, fn {:ok, {key, {:ok, result}}}, acc -> Map.put(acc, key, result) {:ok, {key, result}}, acc -> Map.put(acc, key, result) _, acc -> acc ... (clipped 21 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td align="center" colspan="2"> - [ ] Update <!-- /compliance --update_compliance=true --> </td></tr></tbody></table> <details><summary>Compliance status legend</summary> 🟢 - Fully Compliant<br> 🟡 - Partial Compliant<br> 🔴 - Not Compliant<br> ⚪ - Requires Further Human Verification<br> 🏷️ - Compliance label<br> </details>
qodo-code-review[bot] commented 2026-01-18 17:37:30 +00:00 (Migrated from github.com)
Author
Owner

Imported GitHub PR comment.

Original author: @qodo-code-review[bot]
Original URL: https://github.com/carverauto/serviceradar/pull/2352#issuecomment-3765531338
Original created: 2026-01-18T17:37:30Z

ⓘ Your approaching your monthly quota for Qodo. Upgrade your plan

PR Code Suggestions

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Refactor SRQL parser for GROUP BY

Refactor the SRQL parser to handle the GROUP BY clause structurally. This
involves modifying parser.rs to populate a group_field in StatsSpec,
centralizing parsing logic and removing redundant string parsing from
devices.rs.

Examples:

rust/srql/src/parser.rs [261-293]
                // Handle "by field" part for GROUP BY
                if tokens
                    .peek()
                    .is_some_and(|next| next.as_str().eq_ignore_ascii_case("by"))
                {
                    let _ = tokens.next();
                    let field_token = tokens.next().ok_or_else(|| {
                        ServiceError::InvalidRequest(
                            "stats group by must be of the form 'stats:expr as alias by field'"
                                .into(),

 ... (clipped 23 lines)
rust/srql/src/query/devices.rs [272-321]
fn parse_stats_spec(raw: Option<&str>) -> Result<Option<DeviceStatsSpec>> {
    let raw = match raw {
        Some(raw) if !raw.trim().is_empty() => raw.trim(),
        _ => return Ok(None),
    };

    let tokens: Vec<&str> = raw.split_whitespace().collect();
    if tokens.len() < 3 {
        return Err(ServiceError::InvalidRequest(
            "stats expressions must be of the form 'count() as alias'".into(),

 ... (clipped 40 lines)

Solution Walkthrough:

Before:

// In rust/srql/src/parser.rs
function parse(input):
  // ...
  if token is "stats":
    expr = value
    if next_token is "by":
      field = next_token_after_by
      expr = expr + " by " + field
    stats = new StatsSpec(expr) // Stores raw string
  // ...

// In rust/srql/src/query/devices.rs
function parse_stats_spec(raw_string):
  tokens = raw_string.split_whitespace()
  // Manually parse "count() as alias by field" from tokens
  alias = tokens[2]
  group_field = if tokens.len() >= 5 then Some(tokens[4]) else None
  return new DeviceStatsSpec(alias, group_field)

After:

// In rust/srql/src/parser.rs
// The StatsSpec struct is enhanced
struct StatsSpec {
    //...
    group_field: Option<String>
}

function parse(input):
  // ...
  if token is "stats":
    // ...
    group_field = None
    if next_token is "by":
      group_field = Some(next_token_after_by)
    
    stats = new StatsSpec(..., group_field) // Now structured
  // ...

// In rust/srql/src/query/devices.rs
function build_grouped_stats_query(plan):
  // No more string parsing
  group_field_str = plan.stats.group_field.unwrap()
  group_field = DeviceGroupField::from_str(group_field_str)?
  // ... build query directly

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a significant design flaw where parsing logic is duplicated, making the implementation brittle and hard to extend to other entities, thus addressing a key architectural weakness.

High
Possible issue
Fix incorrect grouped column sorting logic

Fix the grouped column sorting logic by using a substring match instead of an
exact prefix match. This allows sorting by fields like vendor_name even when the
SQL uses COALESCE(vendor_name, 'Unknown').

rust/srql/src/query/devices.rs [397-424]

 fn build_grouped_stats_order_clause(plan: &QueryPlan, alias: &str, group_column: &str) -> String {
     if plan.order.is_empty() {
         return "\nORDER BY COUNT(*) DESC".to_string();
     }
 
     let mut parts = Vec::new();
     for clause in &plan.order {
         let expr = if clause.field.eq_ignore_ascii_case(alias) || clause.field == "count" {
             "COUNT(*)".to_string()
-        } else if clause.field.eq_ignore_ascii_case(group_column.split('(').next().unwrap_or("")) {
+        } else if group_column.to_lowercase().contains(&clause.field.to_lowercase()) {
             group_column.to_string()
         } else {
             continue;
         };
 
         let dir = match clause.direction {
             OrderDirection::Asc => "ASC",
             OrderDirection::Desc => "DESC",
         };
         parts.push(format!("{expr} {dir}"));
     }
 
     if parts.is_empty() {
         "\nORDER BY COUNT(*) DESC".to_string()
     } else {
         format!("\nORDER BY {}", parts.join(", "))
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This is a valid bug fix that corrects the sorting logic for grouped queries, ensuring that user-specified sorting on the grouped field works as intended instead of being silently ignored.

Medium
Escape quotes in filter link generation

In the device_breakdown_card component, escape double quotes within item names
when generating filter links. This prevents malformed SRQL queries if an item
name contains a double quote.

web-ng/lib/serviceradar_web_ng_web/live/device_live/index.ex [1275-1328]

 defp device_breakdown_card(assigns) do
   items = assigns.items || []
   top_item = List.first(items)
   other_count = items |> Enum.drop(1) |> Enum.reduce(0, fn %{count: c}, acc -> acc + c end)
 
   assigns =
     assigns
     |> assign(:top_item, top_item)
     |> assign(:other_count, other_count)
     |> assign(:item_count, length(items))
 
   ~H"""
   <div class="rounded-xl border border-base-200 bg-base-100 p-4 hover:shadow-md transition-shadow">
     <div class="flex items-center gap-3">
       <div class="p-2.5 rounded-lg bg-info/10">
         <.icon name={@icon} class="size-5 text-info" />
       </div>
       <div class="flex-1 min-w-0">
         <div :if={@top_item} class="flex items-baseline gap-1">
           <span class="text-lg font-bold text-base-content truncate max-w-[8rem]" title={@top_item.name}>
             {@top_item.name}
           </span>
           <span class="text-sm text-base-content/60">({@top_item.count})</span>
         </div>
         <div :if={@top_item == nil} class="text-sm text-base-content/40">{@empty_text}</div>
         <div class="text-xs text-base-content/60">
           {@title}
           <span :if={@other_count > 0} class="text-base-content/40">
             · +{@item_count - 1} more
           </span>
         </div>
       </div>
       <div :if={@items != []} class="dropdown dropdown-end">
         <div tabindex="0" role="button" class="btn btn-ghost btn-xs btn-circle">
           <.icon name="hero-chevron-down" class="size-3" />
         </div>
         <ul tabindex="0" class="dropdown-content z-50 menu p-2 shadow-lg bg-base-100 rounded-lg w-52 border border-base-200">
           <%= for item <- Enum.take(@items, 10) do %>
             <li>
               <.link
-                navigate={"/devices?q=" <> URI.encode("in:devices #{@filter_field}:\"#{item.name}\"")}
+                navigate={"/devices?q=" <>
+                  URI.encode("in:devices #{@filter_field}:\"#{String.replace(item.name, "\"", "\\\"")}\"")}
                 class="flex justify-between text-sm"
               >
                 <span class="truncate">{item.name}</span>
                 <span class="badge badge-sm badge-ghost">{item.count}</span>
               </.link>
             </li>
           <% end %>
         </ul>
       </div>
     </div>
   </div>
   """
 end
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This suggestion correctly identifies and fixes a bug where special characters in data would break the generated filter links, improving the robustness of the UI.

Medium
General
Render risk level card

Add a device_breakdown_card to display the by_risk_level statistics, which are
currently fetched but not rendered on the device stats dashboard.

web-ng/lib/serviceradar_web_ng_web/live/device_live/index.ex [1247-1263]

 <!-- Top Device Type -->
 <.device_breakdown_card
   title="By Type"
   items={@by_type}
   icon="hero-cpu-chip"
   filter_field="type_id"
   empty_text="No type data"
 />
 
 <!-- Top Vendor -->
 <.device_breakdown_card
   title="By Vendor"
   items={@by_vendor}
   icon="hero-building-office"
   filter_field="vendor_name"
   empty_text="No vendor data"
 />
 
+<!-- Top Risk Level -->
+<.device_breakdown_card
+  title="By Risk Level"
+  items={@by_risk_level}
+  icon="hero-exclamation-circle"
+  filter_field="risk_level"
+  empty_text="No risk data"
+/>
+
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: This suggestion correctly points out that the fetched by_risk_level data is not being used in the UI. Adding the card completes the feature as described in the PR's design documents.

Low
  • Update
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2352#issuecomment-3765531338 Original created: 2026-01-18T17:37:30Z --- <pre>ⓘ Your approaching your monthly quota for Qodo. <a href="https://www.qodo.ai/pricing">Upgrade your plan</a></pre> ## PR Code Suggestions ✨ <!-- b95482c --> Explore these optional code suggestions: <table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=1>High-level</td> <td> <details><summary>Refactor SRQL parser for GROUP BY</summary> ___ **Refactor the SRQL parser to handle the <code>GROUP BY</code> clause structurally. This <br>involves modifying <code>parser.rs</code> to populate a <code>group_field</code> in <code>StatsSpec</code>, <br>centralizing parsing logic and removing redundant string parsing from <br><code>devices.rs</code>.** ### Examples: <details> <summary> <a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-b2edf55d1721185349ecddb2f4eacc42e0dfcae19b6c2bc638602f187da67e66R261-R293">rust/srql/src/parser.rs [261-293]</a> </summary> ```rust // Handle "by field" part for GROUP BY if tokens .peek() .is_some_and(|next| next.as_str().eq_ignore_ascii_case("by")) { let _ = tokens.next(); let field_token = tokens.next().ok_or_else(|| { ServiceError::InvalidRequest( "stats group by must be of the form 'stats:expr as alias by field'" .into(), ... (clipped 23 lines) ``` </details> <details> <summary> <a href="https://github.com/carverauto/serviceradar/pull/2352/files#diff-3202f22fff6863ed7848a129c49e2323322462b379d896d3fca2e59aa6f7b4c5R272-R321">rust/srql/src/query/devices.rs [272-321]</a> </summary> ```rust fn parse_stats_spec(raw: Option<&str>) -> Result<Option<DeviceStatsSpec>> { let raw = match raw { Some(raw) if !raw.trim().is_empty() => raw.trim(), _ => return Ok(None), }; let tokens: Vec<&str> = raw.split_whitespace().collect(); if tokens.len() < 3 { return Err(ServiceError::InvalidRequest( "stats expressions must be of the form 'count() as alias'".into(), ... (clipped 40 lines) ``` </details> ### Solution Walkthrough: #### Before: ```rust // In rust/srql/src/parser.rs function parse(input): // ... if token is "stats": expr = value if next_token is "by": field = next_token_after_by expr = expr + " by " + field stats = new StatsSpec(expr) // Stores raw string // ... // In rust/srql/src/query/devices.rs function parse_stats_spec(raw_string): tokens = raw_string.split_whitespace() // Manually parse "count() as alias by field" from tokens alias = tokens[2] group_field = if tokens.len() >= 5 then Some(tokens[4]) else None return new DeviceStatsSpec(alias, group_field) ``` #### After: ```rust // In rust/srql/src/parser.rs // The StatsSpec struct is enhanced struct StatsSpec { //... group_field: Option<String> } function parse(input): // ... if token is "stats": // ... group_field = None if next_token is "by": group_field = Some(next_token_after_by) stats = new StatsSpec(..., group_field) // Now structured // ... // In rust/srql/src/query/devices.rs function build_grouped_stats_query(plan): // No more string parsing group_field_str = plan.stats.group_field.unwrap() group_field = DeviceGroupField::from_str(group_field_str)? // ... build query directly ``` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a significant design flaw where parsing logic is duplicated, making the implementation brittle and hard to extend to other entities, thus addressing a key architectural weakness. </details></details></td><td align=center>High </td></tr><tr><td rowspan=2>Possible issue</td> <td> <details><summary>Fix incorrect grouped column sorting logic</summary> ___ **Fix the grouped column sorting logic by using a substring match instead of an <br>exact prefix match. This allows sorting by fields like <code>vendor_name</code> even when the <br>SQL uses <code>COALESCE(vendor_name, 'Unknown')</code>.** [rust/srql/src/query/devices.rs [397-424]](https://github.com/carverauto/serviceradar/pull/2352/files#diff-3202f22fff6863ed7848a129c49e2323322462b379d896d3fca2e59aa6f7b4c5R397-R424) ```diff fn build_grouped_stats_order_clause(plan: &QueryPlan, alias: &str, group_column: &str) -> String { if plan.order.is_empty() { return "\nORDER BY COUNT(*) DESC".to_string(); } let mut parts = Vec::new(); for clause in &plan.order { let expr = if clause.field.eq_ignore_ascii_case(alias) || clause.field == "count" { "COUNT(*)".to_string() - } else if clause.field.eq_ignore_ascii_case(group_column.split('(').next().unwrap_or("")) { + } else if group_column.to_lowercase().contains(&clause.field.to_lowercase()) { group_column.to_string() } else { continue; }; let dir = match clause.direction { OrderDirection::Asc => "ASC", OrderDirection::Desc => "DESC", }; parts.push(format!("{expr} {dir}")); } if parts.is_empty() { "\nORDER BY COUNT(*) DESC".to_string() } else { format!("\nORDER BY {}", parts.join(", ")) } } ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=1 --> <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: This is a valid bug fix that corrects the sorting logic for grouped queries, ensuring that user-specified sorting on the grouped field works as intended instead of being silently ignored. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Escape quotes in filter link generation</summary> ___ **In the <code>device_breakdown_card</code> component, escape double quotes within item names <br>when generating filter links. This prevents malformed SRQL queries if an item <br>name contains a double quote.** [web-ng/lib/serviceradar_web_ng_web/live/device_live/index.ex [1275-1328]](https://github.com/carverauto/serviceradar/pull/2352/files#diff-261a01f4876e5984e1d9e9b38a3540675dfb0272abc30e6bdb2a4fa610353cc7R1275-R1328) ```diff defp device_breakdown_card(assigns) do items = assigns.items || [] top_item = List.first(items) other_count = items |> Enum.drop(1) |> Enum.reduce(0, fn %{count: c}, acc -> acc + c end) assigns = assigns |> assign(:top_item, top_item) |> assign(:other_count, other_count) |> assign(:item_count, length(items)) ~H""" <div class="rounded-xl border border-base-200 bg-base-100 p-4 hover:shadow-md transition-shadow"> <div class="flex items-center gap-3"> <div class="p-2.5 rounded-lg bg-info/10"> <.icon name={@icon} class="size-5 text-info" /> </div> <div class="flex-1 min-w-0"> <div :if={@top_item} class="flex items-baseline gap-1"> <span class="text-lg font-bold text-base-content truncate max-w-[8rem]" title={@top_item.name}> {@top_item.name} </span> <span class="text-sm text-base-content/60">({@top_item.count})</span> </div> <div :if={@top_item == nil} class="text-sm text-base-content/40">{@empty_text}</div> <div class="text-xs text-base-content/60"> {@title} <span :if={@other_count > 0} class="text-base-content/40"> · +{@item_count - 1} more </span> </div> </div> <div :if={@items != []} class="dropdown dropdown-end"> <div tabindex="0" role="button" class="btn btn-ghost btn-xs btn-circle"> <.icon name="hero-chevron-down" class="size-3" /> </div> <ul tabindex="0" class="dropdown-content z-50 menu p-2 shadow-lg bg-base-100 rounded-lg w-52 border border-base-200"> <%= for item <- Enum.take(@items, 10) do %> <li> <.link - navigate={"/devices?q=" <> URI.encode("in:devices #{@filter_field}:\"#{item.name}\"")} + navigate={"/devices?q=" <> + URI.encode("in:devices #{@filter_field}:\"#{String.replace(item.name, "\"", "\\\"")}\"")} class="flex justify-between text-sm" > <span class="truncate">{item.name}</span> <span class="badge badge-sm badge-ghost">{item.count}</span> </.link> </li> <% end %> </ul> </div> </div> </div> """ end ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=2 --> <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: This suggestion correctly identifies and fixes a bug where special characters in data would break the generated filter links, improving the robustness of the UI. </details></details></td><td align=center>Medium </td></tr><tr><td rowspan=1>General</td> <td> <details><summary>Render risk level card</summary> ___ **Add a <code>device_breakdown_card</code> to display the <code>by_risk_level</code> statistics, which are <br>currently fetched but not rendered on the device stats dashboard.** [web-ng/lib/serviceradar_web_ng_web/live/device_live/index.ex [1247-1263]](https://github.com/carverauto/serviceradar/pull/2352/files#diff-261a01f4876e5984e1d9e9b38a3540675dfb0272abc30e6bdb2a4fa610353cc7R1247-R1263) ```diff <!-- Top Device Type --> <.device_breakdown_card title="By Type" items={@by_type} icon="hero-cpu-chip" filter_field="type_id" empty_text="No type data" /> <!-- Top Vendor --> <.device_breakdown_card title="By Vendor" items={@by_vendor} icon="hero-building-office" filter_field="vendor_name" empty_text="No vendor data" /> +<!-- Top Risk Level --> +<.device_breakdown_card + title="By Risk Level" + items={@by_risk_level} + icon="hero-exclamation-circle" + filter_field="risk_level" + empty_text="No risk data" +/> + ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=3 --> <details><summary>Suggestion importance[1-10]: 5</summary> __ Why: This suggestion correctly points out that the fetched `by_risk_level` data is not being used in the UI. Adding the card completes the feature as described in the PR's design documents. </details></details></td><td align=center>Low </td></tr> <tr><td align="center" colspan="2"> - [ ] Update <!-- /improve_multi --more_suggestions=true --> </td><td></td></tr></tbody></table>
qodo-code-review[bot] commented 2026-01-18 19:33:11 +00:00 (Migrated from github.com)
Author
Owner

Imported GitHub PR comment.

Original author: @qodo-code-review[bot]
Original URL: https://github.com/carverauto/serviceradar/pull/2352#issuecomment-3765653531
Original created: 2026-01-18T19:33:11Z

CI Feedback 🧐

(Feedback updated until commit github.com/carverauto/serviceradar@5728afb674)

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: build

Failed stage: Configure SRQL fixture database for tests []

Failed test name: ""

Failure summary:

The action failed during environment/fixture setup because the required secret
SRQL_TEST_DATABASE_CA_CERT was not configured.
- The log explicitly reports:
SRQL_TEST_DATABASE_CA_CERT secret must be configured to verify SRQL fixture TLS. (around lines
636–638).
- The job then exited with code 1, stopping the workflow before any tests could run.

Relevant error logs:
1:  Runner name: 'arc-runner-set-hk6mk-runner-9fggh'
2:  Runner group name: 'Default'
...

139:  ^[[36;1mif command -v apt-get >/dev/null 2>&1; then^[[0m
140:  ^[[36;1m  sudo apt-get update^[[0m
141:  ^[[36;1m  sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler cmake flex bison^[[0m
142:  ^[[36;1melif command -v dnf >/dev/null 2>&1; then^[[0m
143:  ^[[36;1m  sudo dnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m
144:  ^[[36;1melif command -v yum >/dev/null 2>&1; then^[[0m
145:  ^[[36;1m  sudo yum install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m
146:  ^[[36;1melif command -v microdnf >/dev/null 2>&1; then^[[0m
147:  ^[[36;1m  sudo microdnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m
148:  ^[[36;1melse^[[0m
149:  ^[[36;1m  echo "Unsupported package manager; please install gcc, g++ (or clang), make, OpenSSL headers, pkg-config, and protoc manually." >&2^[[0m
150:  ^[[36;1m  exit 1^[[0m
151:  ^[[36;1mfi^[[0m
152:  ^[[36;1m^[[0m
153:  ^[[36;1mensure_pkg_config^[[0m
154:  ^[[36;1mprotoc --version || (echo "protoc installation failed" && exit 1)^[[0m
155:  shell: /usr/bin/bash -e {0}
...

316:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
317:  env:
318:  BUILDBUDDY_ORG_API_KEY: ***
319:  SRQL_TEST_DATABASE_URL: ***
320:  SRQL_TEST_ADMIN_URL: ***
321:  SRQL_TEST_DATABASE_CA_CERT: 
322:  DOCKERHUB_USERNAME: ***
323:  DOCKERHUB_TOKEN: ***
324:  TEST_CNPG_DATABASE: serviceradar_web_ng_test
325:  INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp
326:  INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir
327:  ##[endgroup]
328:  ##[group]Run : install rustup if needed
329:  ^[[36;1m: install rustup if needed^[[0m
330:  ^[[36;1mif ! command -v rustup &>/dev/null; then^[[0m
331:  ^[[36;1m  curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail https://sh.rustup.rs | sh -s -- --default-toolchain none -y^[[0m
332:  ^[[36;1m  echo "$CARGO_HOME/bin" >> $GITHUB_PATH^[[0m
...

472:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
473:  env:
474:  BUILDBUDDY_ORG_API_KEY: ***
475:  SRQL_TEST_DATABASE_URL: ***
476:  SRQL_TEST_ADMIN_URL: ***
477:  SRQL_TEST_DATABASE_CA_CERT: 
478:  DOCKERHUB_USERNAME: ***
479:  DOCKERHUB_TOKEN: ***
480:  TEST_CNPG_DATABASE: serviceradar_web_ng_test
481:  INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp
482:  INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir
483:  CARGO_HOME: /home/runner/.cargo
484:  CARGO_INCREMENTAL: 0
485:  CARGO_TERM_COLOR: always
486:  ##[endgroup]
487:  ##[group]Run : work around spurious network errors in curl 8.0
488:  ^[[36;1m: work around spurious network errors in curl 8.0^[[0m
489:  ^[[36;1m# https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/timeout.20investigation^[[0m
...

540:  SRQL_TEST_DATABASE_CA_CERT: 
541:  DOCKERHUB_USERNAME: ***
542:  DOCKERHUB_TOKEN: ***
543:  TEST_CNPG_DATABASE: serviceradar_web_ng_test
544:  INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp
545:  INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir
546:  CARGO_HOME: /home/runner/.cargo
547:  CARGO_INCREMENTAL: 0
548:  CARGO_TERM_COLOR: always
549:  ##[endgroup]
550:  Attempting to download 1.x...
551:  Acquiring v1.28.0 from https://github.com/bazelbuild/bazelisk/releases/download/v1.28.0/bazelisk-linux-amd64
552:  Adding to the cache ...
553:  Successfully cached bazelisk to /home/runner/_work/_tool/bazelisk/1.28.0/x64
554:  Added bazelisk to the path
555:  ##[warning]Failed to restore: Cache service responded with 400
556:  Restored bazelisk cache dir @ /home/runner/.cache/bazelisk
...

622:  env:
623:  BUILDBUDDY_ORG_API_KEY: ***
624:  SRQL_TEST_DATABASE_URL: ***
625:  SRQL_TEST_ADMIN_URL: ***
626:  SRQL_TEST_DATABASE_CA_CERT: 
627:  DOCKERHUB_USERNAME: ***
628:  DOCKERHUB_TOKEN: ***
629:  TEST_CNPG_DATABASE: serviceradar_web_ng_test
630:  INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp
631:  INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir
632:  CARGO_HOME: /home/runner/.cargo
633:  CARGO_INCREMENTAL: 0
634:  CARGO_TERM_COLOR: always
635:  ##[endgroup]
636:  SRQL_TEST_DATABASE_CA_CERT secret must be configured to verify SRQL fixture TLS.
637:  ##[error]Process completed with exit code 1.
638:  Post job cleanup.

Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2352#issuecomment-3765653531 Original created: 2026-01-18T19:33:11Z --- ## CI Feedback 🧐 #### (Feedback updated until commit https://github.com/carverauto/serviceradar/commit/5728afb67462169abcdd9abdb17e92b1a9c9c431) A test triggered by this PR failed. Here is an AI-generated analysis of the failure: <table><tr><td> **Action:** build</td></tr> <tr><td> **Failed stage:** [Configure SRQL fixture database for tests](https://github.com/carverauto/serviceradar/actions/runs/21117395850/job/60724947446) [❌] </td></tr> <tr><td> **Failed test name:** "" </td></tr> <tr><td> **Failure summary:** The action failed during environment/fixture setup because the required secret <br><code>SRQL_TEST_DATABASE_CA_CERT</code> was not configured.<br> - The log explicitly reports: <br><code>SRQL_TEST_DATABASE_CA_CERT secret must be configured to verify SRQL fixture TLS.</code> (around lines <br>636–638).<br> - The job then exited with code <code>1</code>, stopping the workflow before any tests could run.<br> </td></tr> <tr><td> <details><summary>Relevant error logs:</summary> ```yaml 1: Runner name: 'arc-runner-set-hk6mk-runner-9fggh' 2: Runner group name: 'Default' ... 139: ^[[36;1mif command -v apt-get >/dev/null 2>&1; then^[[0m 140: ^[[36;1m sudo apt-get update^[[0m 141: ^[[36;1m sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler cmake flex bison^[[0m 142: ^[[36;1melif command -v dnf >/dev/null 2>&1; then^[[0m 143: ^[[36;1m sudo dnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m 144: ^[[36;1melif command -v yum >/dev/null 2>&1; then^[[0m 145: ^[[36;1m sudo yum install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m 146: ^[[36;1melif command -v microdnf >/dev/null 2>&1; then^[[0m 147: ^[[36;1m sudo microdnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m 148: ^[[36;1melse^[[0m 149: ^[[36;1m echo "Unsupported package manager; please install gcc, g++ (or clang), make, OpenSSL headers, pkg-config, and protoc manually." >&2^[[0m 150: ^[[36;1m exit 1^[[0m 151: ^[[36;1mfi^[[0m 152: ^[[36;1m^[[0m 153: ^[[36;1mensure_pkg_config^[[0m 154: ^[[36;1mprotoc --version || (echo "protoc installation failed" && exit 1)^[[0m 155: shell: /usr/bin/bash -e {0} ... 316: shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0} 317: env: 318: BUILDBUDDY_ORG_API_KEY: *** 319: SRQL_TEST_DATABASE_URL: *** 320: SRQL_TEST_ADMIN_URL: *** 321: SRQL_TEST_DATABASE_CA_CERT: 322: DOCKERHUB_USERNAME: *** 323: DOCKERHUB_TOKEN: *** 324: TEST_CNPG_DATABASE: serviceradar_web_ng_test 325: INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp 326: INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir 327: ##[endgroup] 328: ##[group]Run : install rustup if needed 329: ^[[36;1m: install rustup if needed^[[0m 330: ^[[36;1mif ! command -v rustup &>/dev/null; then^[[0m 331: ^[[36;1m curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail https://sh.rustup.rs | sh -s -- --default-toolchain none -y^[[0m 332: ^[[36;1m echo "$CARGO_HOME/bin" >> $GITHUB_PATH^[[0m ... 472: shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0} 473: env: 474: BUILDBUDDY_ORG_API_KEY: *** 475: SRQL_TEST_DATABASE_URL: *** 476: SRQL_TEST_ADMIN_URL: *** 477: SRQL_TEST_DATABASE_CA_CERT: 478: DOCKERHUB_USERNAME: *** 479: DOCKERHUB_TOKEN: *** 480: TEST_CNPG_DATABASE: serviceradar_web_ng_test 481: INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp 482: INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir 483: CARGO_HOME: /home/runner/.cargo 484: CARGO_INCREMENTAL: 0 485: CARGO_TERM_COLOR: always 486: ##[endgroup] 487: ##[group]Run : work around spurious network errors in curl 8.0 488: ^[[36;1m: work around spurious network errors in curl 8.0^[[0m 489: ^[[36;1m# https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/timeout.20investigation^[[0m ... 540: SRQL_TEST_DATABASE_CA_CERT: 541: DOCKERHUB_USERNAME: *** 542: DOCKERHUB_TOKEN: *** 543: TEST_CNPG_DATABASE: serviceradar_web_ng_test 544: INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp 545: INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir 546: CARGO_HOME: /home/runner/.cargo 547: CARGO_INCREMENTAL: 0 548: CARGO_TERM_COLOR: always 549: ##[endgroup] 550: Attempting to download 1.x... 551: Acquiring v1.28.0 from https://github.com/bazelbuild/bazelisk/releases/download/v1.28.0/bazelisk-linux-amd64 552: Adding to the cache ... 553: Successfully cached bazelisk to /home/runner/_work/_tool/bazelisk/1.28.0/x64 554: Added bazelisk to the path 555: ##[warning]Failed to restore: Cache service responded with 400 556: Restored bazelisk cache dir @ /home/runner/.cache/bazelisk ... 622: env: 623: BUILDBUDDY_ORG_API_KEY: *** 624: SRQL_TEST_DATABASE_URL: *** 625: SRQL_TEST_ADMIN_URL: *** 626: SRQL_TEST_DATABASE_CA_CERT: 627: DOCKERHUB_USERNAME: *** 628: DOCKERHUB_TOKEN: *** 629: TEST_CNPG_DATABASE: serviceradar_web_ng_test 630: INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp 631: INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir 632: CARGO_HOME: /home/runner/.cargo 633: CARGO_INCREMENTAL: 0 634: CARGO_TERM_COLOR: always 635: ##[endgroup] 636: SRQL_TEST_DATABASE_CA_CERT secret must be configured to verify SRQL fixture TLS. 637: ##[error]Process completed with exit code 1. 638: Post job cleanup. ``` </details></td></tr></table>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
carverauto/serviceradar!2698
No description provided.