Skip to content

Conversation

@visz11
Copy link

@visz11 visz11 commented Nov 10, 2025

CodeAnt-AI Description

Display cycle-time metrics in hours and preserve control-chart state in MetricDetails

What Changed

  • Cycle-time and other time-based metrics (e.g., PR/merge/coding cycle, lead times, delivery) now show averages and data points converted from minutes to hours; chart keys and labels display "Hours".
  • Custom metrics keep their original numeric values and labels (no conversion).
  • Control-chart toggle is read from and written to the query state so the control-chart setting persists and is applied when fetching chart data.
  • MetricDetails includes breadcrumb/back navigation and an info drawer showing chart title and metadata; a reload action refetches chart data.

Impact

✅ Clearer cycle-time charts (values shown in hours instead of minutes)
✅ Consistent unit labels in chart legends and averages
✅ Control-chart setting persists from URL and applies to chart data

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by CodeRabbit

  • New Features
    • Introduced a new metric details interface featuring comprehensive data display with interactive chart controls, team and sprint filtering options, customizable breakdowns, and enhanced navigation capabilities.

@codeant-ai
Copy link

codeant-ai bot commented Nov 10, 2025

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link

coderabbitai bot commented Nov 10, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds MetricDetails, a new React functional component in index.tsx that renders a metric detail view with TopSection, BottomSection, conditional navigation elements, and optional SlideInfoDrawer. Manages complex state through multiple hooks, fetches data via GraphQL with dynamic parameters, transforms data based on chart keys, and handles state synchronization and user interactions.

Changes

Cohort / File(s) Change Summary
New MetricDetails Component
index.tsx
Introduces MetricDetails functional component as public export. Integrates routing, query state management, and dashboard store hooks. Implements GraphQL data fetching via useDetailChartsGql with dynamic parameters (dates, metric type, breakdown, team, sprint, project, view, repos, showControlChart). Manages data transformation based on chart keys and types. Handles drawer state, query parameter synchronization, and conditional rendering of navigation (Breadcrumbs or Back button) and optional SlideInfoDrawer.

Sequence Diagram

sequenceDiagram
    actor User
    participant MetricDetails as MetricDetails<br/>(Component)
    participant Hooks as Routing/State Hooks
    participant GraphQL as GraphQL Query<br/>(useDetailChartsGql)
    participant Store as Dashboard Store
    participant UI as UI Sections<br/>(Top/Bottom)

    User->>MetricDetails: Navigate to metric view
    MetricDetails->>Hooks: Consume routing, query params
    MetricDetails->>Store: Read initial team & state
    MetricDetails->>Hooks: Initialize query state (start, end, duration, sprint, etc.)
    MetricDetails->>GraphQL: Fetch chart data with dynamic params
    GraphQL-->>MetricDetails: Return chart data
    MetricDetails->>MetricDetails: Transform data (apply chart keys, data types)
    MetricDetails->>UI: Render TopSection & BottomSection<br/>with transformed data
    UI-->>User: Display metric details view
    User->>MetricDetails: Interact (change date, toggle chart, open drawer)
    MetricDetails->>Hooks: Update query state & sync params
    MetricDetails->>GraphQL: Refetch with new parameters
    GraphQL-->>MetricDetails: Return updated data
    MetricDetails->>UI: Re-render with new data
    UI-->>User: Show updated metrics
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Dense component with multiple hook integrations (routing, query state, dashboard store) requiring careful verification of state synchronization logic
  • Complex GraphQL query with many dynamic parameters—verify correct parameter passing and edge cases
  • Data transformation logic needs validation to ensure chart keys and data type conversions work correctly
  • Multiple conditional rendering branches (Breadcrumbs vs. Back button, optional SlideInfoDrawer) should be validated against route conditions
  • Interaction handlers (drawer state, refetch triggers) and their side effects should be checked for correctness

Poem

🐰 A metric's journey, now on display,
With TopSection, Bottom, the data at play,
Hooks dancing together in harmony bright,
Details unfolding—what a delightful sight!
hops 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: adding a new MetricDetails component with chart functionality, which is the primary addition in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch Fix-Cycle-Time-metrics-breakdown-by-project-do-incorrect-conversion

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @visz11, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new MetricDetails component, providing a dedicated page for viewing detailed metric charts. It focuses on enhancing user experience by integrating URL query parameters for dynamic state management, ensuring that chart filters are reflected in the URL. A key feature is the transformation of time-based metric data from minutes to hours, making the displayed information more intuitive. The component is structured modularly, leveraging existing UI elements and utility functions to present a comprehensive and interactive metric visualization.

Highlights

  • New MetricDetails Component: Introduced a new MetricDetails React component to display detailed metric charts, serving as a central view for various performance indicators.
  • URL Query Parameter Integration: Implemented robust state management by synchronizing chart filters such as start/end dates, duration, team, and breakdown with URL query parameters, allowing for shareable and persistent views.
  • Time-Based Metric Transformation: Added logic to automatically convert specific time-based metric values (e.g., cycle times, lead times) from minutes to hours for improved readability and consistency in chart displays.
  • Modular UI Structure: Utilized TopSection and BottomSection sub-components to organize the layout and functionality of the metric details page, along with a SlideInfoDrawer for additional information.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codeant-ai codeant-ai bot added the size:L This PR changes 100-499 lines, ignoring generated files label Nov 10, 2025
@codeant-ai
Copy link

codeant-ai bot commented Nov 10, 2025

CodeAnt AI finished reviewing your PR.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new MetricDetails component for displaying chart data. The implementation is comprehensive but has several issues that need addressing. I've identified a critical syntax error that will prevent the code from running, along with high-severity issues like type mismatches and typos in prop names that would lead to runtime errors. Additionally, there are several medium-severity suggestions to improve code quality, maintainability, and robustness, such as removing unused code, avoiding magic numbers, and improving hook usage. Please review the detailed comments for specific suggestions.

Comment on lines +138 to +139
return convertedItem;
}),

Choose a reason for hiding this comment

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

critical

These lines introduce a syntax error within the modifiedData object definition. They appear to be leftover code from a refactoring and should be removed to make the code valid.

const [periodOptions, setPeriodOptions] = useState<any>(durationData);

const [viewToggle, setViewToggle] = useState<string>('Week');
const [repos, setRepos] = useState<string[]>([]);

Choose a reason for hiding this comment

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

high

The repos state is initialized with type string[], but it's used as an array of objects on line 73 (e.g., repos.map((el: any) => el.value)). This type mismatch will cause a runtime error. Based on the usage and the ISelect import, the type should be ISelect[].

  const [repos, setRepos] = useState<ISelect[]>([]);

</Box>
{openDrawer && (
<SlideInfoDrawer
handelDrawerClose={handleDrawerClose}

Choose a reason for hiding this comment

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

high

There is a typo in the prop name handelDrawerClose. The function defined on line 45 is handleDrawerClose. This mismatch will cause a runtime error when the user tries to close the drawer. The prop name should be corrected to handleDrawerClose.

          handleDrawerClose={handleDrawerClose}

initialTeam?: string;
}

export const MetricDetails = ({ initialTeam }: metricDetailProps) => {

Choose a reason for hiding this comment

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

medium

The prop initialTeam is destructured here but is never used within the component. To keep the component's API clean and avoid confusion, it should be removed from both the component's signature and the metricDetailProps interface (line 20).

export const MetricDetails = ({}: metricDetailProps) => {

Comment on lines +45 to +48
const handleDrawerClose = useCallback(
() => setOpenDrawer(false),
[openDrawer]
);

Choose a reason for hiding this comment

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

medium

The dependency array for this useCallback includes openDrawer, but the callback function () => setOpenDrawer(false) does not depend on the openDrawer value itself. The setter function from useState is stable. Using an empty dependency array [] is more correct here and avoids unnecessarily recreating the function on every render.

  const handleDrawerClose = useCallback(
    () => setOpenDrawer(false),
    []
  );

showControlChart
);

const handelReloadClick = async () => {

Choose a reason for hiding this comment

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

medium

There's a typo in the function name handelReloadClick. For consistency and correctness, it should be handleReloadClick. Remember to also update this where it's passed as a prop to BottomSection on line 270.

  const handleReloadClick = async () => {

Comment on lines +92 to +108
const modifiedData = [
'MEAN_TIME_TO_RECOVERY',
'LEAD_TIME_FOR_CHANGE',
'PR_CYCLE_TIME',
'CODING_CYCLE_TIME',
'PICKUP_CYCLE_TIME',
'MERGE_CYCLE_TIME',
'TASK_LEAD_TIME',
'BUG_LEAD_TIME',
'ISSUE_CYCLE_TIME',
'MEETING_TIME',
'DEPLOY_CYCLE_TIME',
'DELIVERY_LEAD_TIME',
'BACKLOG_CYCLE_TIME',
'TEST_RUN_TIME',
'CUSTOM',
].includes(data?.chartMetadata?.chartKey)

Choose a reason for hiding this comment

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

medium

This large array of strings is hardcoded within the component's render logic. To improve readability and maintainability, it's best to extract this into a named constant defined outside the component, for example: const TIME_BASED_METRIC_KEYS = [...].

Comment on lines +186 to +211
periodOptions
.filter(
(item: any) =>
item.duration === duration ||
item.duration === selected.duration
)
.map((el: any) =>
el.duration === 'Custom' ||
el.duration === 'Today' ||
el.duration === 'Month' ||
el.duration === 'Quarter'
? {
...el,
startDate: startDate || selected.startDate,
endDate: endDate || selected.endDate,
}
: el.duration === 'Sprint'
? {
...el,
startDate: startDate || selected.startDate,
endDate: endDate || selected.endDate,
sprintId: sprintId || selected.sprintId,
sprintName: sprintName || selected.sprintName,
}
: el
)[0]

Choose a reason for hiding this comment

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

medium

Chaining .filter(...).map(...)[0] is unsafe. If the .filter() call returns an empty array, attempting to access the first element with [0] will throw a TypeError at runtime. A safer approach would be to use .find() to retrieve the item directly, or to check the length of the filtered array before accessing an element.

: el
)[0]
)
: setSelected(periodOptions[3]);

Choose a reason for hiding this comment

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

medium

Using a magic number 3 to select the default period option is brittle and makes the code harder to understand. If the order of periodOptions changes, this could select the wrong option or cause an error. It's better to find the default option by a unique property (e.g., its duration value) or to define a constant for the default value.

@visz11
Copy link
Author

visz11 commented Nov 10, 2025

/refacto-visz

@refacto-visz
Copy link

refacto-visz bot commented Nov 10, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

Copy link

@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

🧹 Nitpick comments (1)
index.tsx (1)

45-48: Remove unnecessary dependency from useCallback.

The handleDrawerClose callback includes openDrawer in its dependency array but doesn't reference it, causing unnecessary re-renders.

Apply this diff:

   const handleDrawerClose = useCallback(
     () => setOpenDrawer(false),
-    [openDrawer]
+    []
   );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e184110 and 0c2e50e.

📒 Files selected for processing (1)
  • index.tsx (1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
index.tsx

[error] 138-138: expected : but instead found convertedItem

Remove convertedItem

(parse)


[error] 138-138: expected , but instead found ;

Remove ;

(parse)


[error] 139-139: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 141-141: Expected an expression but instead found '...key, name: 'Hours''.

Expected an expression here.

(parse)


[error] 141-145: Expected a statement but instead found ': key
),
refLines: data?.refLines,
}
: data'.

Expected a statement here.

(parse)


[error] 228-285: Illegal return statement outside of a function

(parse)


[error] 285-286: Expected a statement but instead found '}'.

Expected a statement here.

(parse)

🔇 Additional comments (4)
index.tsx (4)

177-220: Review initialization logic and dependencies.

This initialization effect has an empty dependency array but reads several external values (team, duration, selected.duration, etc.). This pattern works for one-time initialization from URL params, but ensure these values are available when the effect runs.

Verify the initialization order and that query params are ready when this effect executes. Consider whether this logic should be split or if certain dependencies should be included.


147-175: LGTM: Query state synchronization.

The effect correctly syncs component state to URL query parameters, properly handling empty values with removeEmptyQueryParams.


84-90: LGTM: View toggle logic.

Correctly adjusts the view granularity based on the selected duration.


228-285: LGTM: Component structure and rendering.

The component structure is well-organized with appropriate conditional rendering and prop passing.

Comment on lines +19 to +23
interface metricDetailProps {
initialTeam?: string;
}

export const MetricDetails = ({ initialTeam }: metricDetailProps) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify unused initialTeam prop.

The initialTeam prop is defined but never used in the component. Confirm whether this should be used to initialize the team selection or can be removed.

Search for how this component is used and whether initialTeam is passed:


🏁 Script executed:

#!/bin/bash
# Find usages of MetricDetails component
rg -nP --type=tsx --type=ts -C3 '<MetricDetails|MetricDetails\s*\('

Length of output: 27


Now let me run the corrected shell script to search for MetricDetails usages and initialTeam prop usage.


🏁 Script executed:

#!/bin/bash
# Correct approach: use glob pattern for TypeScript files
# Find MetricDetails component usages
rg -nP -C3 'MetricDetails' -g "*.tsx" -g "*.ts"

Length of output: 354


🏁 Script executed:

#!/bin/bash
# Search for MetricDetails component instantiation (opening tag)
echo "=== MetricDetails component usages (instantiations) ==="
rg -nP '<MetricDetails' -g "*.tsx" -g "*.ts"

echo -e "\n=== initialTeam references in the file ==="
# Search for initialTeam anywhere in the file
rg -nP 'initialTeam' -g "*.tsx" -g "*.ts"

Length of output: 220


Remove unused initialTeam prop.

Verification confirms that the initialTeam prop is never referenced in the component body and no parent components pass it. This should be removed from both the interface definition and the component signature.

🤖 Prompt for AI Agents
In index.tsx around lines 19 to 23, the MetricDetails component declares an
unused initialTeam prop in the metricDetailProps interface and component
signature; remove initialTeam from the interface and update the MetricDetails
declaration to accept no props (or an empty props object) so the prop is no
longer defined or expected, then run TypeScript/linters to confirm no remaining
references.

Comment on lines +112 to +118
data?.average && data?.chartMetadata?.dataType == 'INTEGER'
? data?.average
: data?.average && data?.chartMetadata?.dataType == 'TIME'
? splitTime(parseInt(data?.average) / 60)
: data?.average && data?.chartMetadata?.dataType == 'PERCENTAGE'
? data?.average
: data?.average && splitTime(parseInt(data?.average) / 60),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use strict equality operators.

The dataType comparisons use loose equality (==) instead of strict equality (===), which can lead to unexpected type coercion.

Apply this diff:

         average:
-          data?.average && data?.chartMetadata?.dataType == 'INTEGER'
+          data?.average && data?.chartMetadata?.dataType === 'INTEGER'
             ? data?.average
-            : data?.average && data?.chartMetadata?.dataType == 'TIME'
+            : data?.average && data?.chartMetadata?.dataType === 'TIME'
             ? splitTime(parseInt(data?.average) / 60)
-            : data?.average && data?.chartMetadata?.dataType == 'PERCENTAGE'
+            : data?.average && data?.chartMetadata?.dataType === 'PERCENTAGE'
             ? data?.average
             : data?.average && splitTime(parseInt(data?.average) / 60),
📝 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
data?.average && data?.chartMetadata?.dataType == 'INTEGER'
? data?.average
: data?.average && data?.chartMetadata?.dataType == 'TIME'
? splitTime(parseInt(data?.average) / 60)
: data?.average && data?.chartMetadata?.dataType == 'PERCENTAGE'
? data?.average
: data?.average && splitTime(parseInt(data?.average) / 60),
data?.average && data?.chartMetadata?.dataType === 'INTEGER'
? data?.average
: data?.average && data?.chartMetadata?.dataType === 'TIME'
? splitTime(parseInt(data?.average) / 60)
: data?.average && data?.chartMetadata?.dataType === 'PERCENTAGE'
? data?.average
: data?.average && splitTime(parseInt(data?.average) / 60),
🤖 Prompt for AI Agents
index.tsx around lines 112 to 118: the dataType comparisons use loose equality
(==); replace each occurrence of == with strict equality operator === so
comparisons use exact type-and-value matching (e.g.,
data?.chartMetadata?.dataType === 'INTEGER', 'TIME', 'PERCENTAGE'), leaving the
surrounding conditional logic unchanged.

Comment on lines +132 to +135
clusterP90ToP95: Number(item.clusterP90ToP95) / 60 || 0,
clusterP95ToP100: Number(item.clusterP95ToP100) / 60 || 0,
clusterP0ToP5: Number(item.clusterP0ToP5) / 60 || 0,
clusterP5ToP10: Number(item.clusterP5ToP10) / 60 || 0,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify cluster value transformations for CUSTOM metrics.

Lines 132-135 unconditionally divide all cluster percentile values by 60, but lines 129-131 skip this division for CUSTOM chart keys. This inconsistency may cause misaligned data in control charts for custom metrics.

Verify whether cluster values should also respect the CUSTOM chartKey condition:

If cluster values should match the y-value behavior, apply this diff:

           y:
             data?.chartMetadata?.chartKey === 'CUSTOM'
               ? Number(item.y)
               : Number(item.y) / 60,
-          clusterP90ToP95: Number(item.clusterP90ToP95) / 60 || 0,
-          clusterP95ToP100: Number(item.clusterP95ToP100) / 60 || 0,
-          clusterP0ToP5: Number(item.clusterP0ToP5) / 60 || 0,
-          clusterP5ToP10: Number(item.clusterP5ToP10) / 60 || 0,
+          clusterP90ToP95: data?.chartMetadata?.chartKey === 'CUSTOM'
+            ? Number(item.clusterP90ToP95) || 0
+            : Number(item.clusterP90ToP95) / 60 || 0,
+          clusterP95ToP100: data?.chartMetadata?.chartKey === 'CUSTOM'
+            ? Number(item.clusterP95ToP100) || 0
+            : Number(item.clusterP95ToP100) / 60 || 0,
+          clusterP0ToP5: data?.chartMetadata?.chartKey === 'CUSTOM'
+            ? Number(item.clusterP0ToP5) || 0
+            : Number(item.clusterP0ToP5) / 60 || 0,
+          clusterP5ToP10: data?.chartMetadata?.chartKey === 'CUSTOM'
+            ? Number(item.clusterP5ToP10) || 0
+            : Number(item.clusterP5ToP10) / 60 || 0,

🏁 Script executed:

#!/bin/bash
# Search for how clusterP90ToP95 and similar fields are used in chart rendering
rg -nP --type=tsx --type=ts -C3 'clusterP(90|95|0|5)To'

Length of output: 27



🏁 Script executed:

#!/bin/bash
# Search for clusterP90ToP95 usage with corrected type filter
rg -nP --type ts -C3 'clusterP(90|95|0|5)To'

Length of output: 572



🏁 Script executed:

#!/bin/bash
# Search for where cluster values are used in chart rendering/consumption
rg -nP --type ts 'clusterP|clusterP90' -A2 -B2 | head -60

Length of output: 457



🏁 Script executed:

#!/bin/bash
# Find broader context of CUSTOM metric handling and where cluster fields might be used
rg -nP --type ts "CUSTOM|clusterP|clusterP90" -B5 -A5 | grep -E "(chartKey|CUSTOM|render|chart|cluster)" -i

Length of output: 770



🏁 Script executed:

#!/bin/bash
# Find what happens after the data transformation (after line 136)
rg -nP --type ts -A20 'clusterP5ToP10:' | head -40

Length of output: 993



🏁 Script executed:

#!/bin/bash
# Find where convertedItem or the return value is consumed
rg -nP --type ts -B10 "return convertedItem" | head -30

Length of output: 599



🏁 Script executed:

#!/bin/bash
# Find function context and where this data transformation happens
rg -nP --type ts -B30 'clusterP90ToP95:' | grep -E "(function|const|=>|export)" | tail -5

Length of output: 60



🏁 Script executed:

#!/bin/bash
# Get broader context to find function name and full transformation logic
rg -nP --type ts -B50 'clusterP90ToP95:' | grep -E "^index.tsx-[0-9]+-\s*(export|function|const|if)" | tail -10

Length of output: 92



🏁 Script executed:

#!/bin/bash
# Search for tests or comments explaining cluster value behavior
rg -nP --type ts "cluster|CUSTOM" -B2 -A2 | grep -E "(test|spec|describe|it\(|TODO|FIXME|NOTE|cluster)" -i | head -40

Length of output: 354


Cluster values should respect CUSTOM metric condition like y values do.

The inconsistency is confirmed. Lines 129-131 show y values conditionally skip division by 60 when chartKey === 'CUSTOM', and lines 140-142 show keys are also conditionally transformed for unit handling. However, lines 132-135 unconditionally divide all cluster percentile values by 60, ignoring the CUSTOM condition. This breaks semantic consistency in unit scaling for custom metrics. The proposed fix aligns with the broader transformation pattern in this code.

🤖 Prompt for AI Agents
In index.tsx around lines 132 to 135, the cluster percentile values are always
divided by 60 which ignores the CUSTOM metric case; change these lines to follow
the same conditional used for y values and unit-handled keys so that when
chartKey === 'CUSTOM' you do not divide by 60 (use the raw
Number(item.clusterPxx) || 0), otherwise divide by 60 as currently done,
ensuring consistency with the surrounding transformations.

Comment on lines +138 to +139
return convertedItem;
}),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical syntax error: Remove stray return statement.

Lines 138-139 contain a return convertedItem; statement that is syntactically invalid within the object literal. This prevents the code from compiling and is flagged by the static analysis tool.

Apply this diff to remove the invalid statement:

           clusterP0ToP5: Number(item.clusterP0ToP5) / 60 || 0,
           clusterP5ToP10: Number(item.clusterP5ToP10) / 60 || 0,
         })),
-
-          return convertedItem;
-        }),
         keys: data?.keys?.map((key: any) =>
           key.name === 'Minutes' ? { ...key, name: 'Hours' } : key
         ),
📝 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
return convertedItem;
}),
clusterP0ToP5: Number(item.clusterP0ToP5) / 60 || 0,
clusterP5ToP10: Number(item.clusterP5ToP10) / 60 || 0,
})),
keys: data?.keys?.map((key: any) =>
key.name === 'Minutes' ? { ...key, name: 'Hours' } : key
),
🧰 Tools
🪛 Biome (2.1.2)

[error] 138-138: expected : but instead found convertedItem

Remove convertedItem

(parse)


[error] 138-138: expected , but instead found ;

Remove ;

(parse)


[error] 139-139: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)

🤖 Prompt for AI Agents
index.tsx around lines 138 to 139: there is a stray "return convertedItem;"
inside an object literal which is a syntax error; remove that line so the object
literal is valid and ensure the surrounding map/arrow function returns the
convertedItem value correctly (either return the object from the arrow function
or use an expression body), then run TypeScript/ESLint to verify the compile
error is resolved.

@refacto-visz
Copy link

refacto-visz bot commented Nov 10, 2025

Code Review: MetricDetails Component - Critical Data Mapping Issues

👍 Well Done
Comprehensive State Management

Query state persistence ensures consistent user experience across navigation

Hook Usage Pattern

Custom hooks useQueryState and useDashboardStore promote state reusability effectively

📁 Selected files for review (1)
  • index.tsx
🎯 Custom Instructions
✅ Applied Instructions
Organization Guidelines
  • Keep pull requests small and focused (prefer < 400 lines of code).
  • All CI/CD checks, linting, and unit tests must pass before merge.
  • Use feature flags for new functionality and include a clear rollback plan.
  • Follow the company security checklist:
    • No hard-coded secrets or credentials.
    • Validate all external inputs.
    • Use parameterized queries for DB access.

Scope: All files

📝 Additional Comments
index.tsx (5)
Complex Conditional Logic

Nested ternary operations with repeated property access create inefficient conditional evaluation chains. Multiple data?.average checks and data?.chartMetadata?.dataType evaluations cause redundant property lookups. Refactor using switch statement or lookup table for better performance.

Standards:

  • ISO-IEC-25010-Performance-Efficiency-Time-Behavior
  • Optimization-Pattern-Conditional-Logic
  • Code-Complexity-Reduction
Unsafe Type Coercion

Number coercion without null/undefined validation may produce NaN values when cluster properties are missing. NaN propagation through mathematical operations corrupts chart calculations. Chart rendering fails with invalid numeric data causing visualization errors and degraded user experience.

Standards:

  • ISO-IEC-25010-Reliability-Maturity
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
Expensive Array Operations

Chained filter and map operations with complex conditional logic create O(n) processing overhead in useEffect. Multiple string comparisons and object spread operations cause unnecessary computational cost. Consider memoizing or optimizing lookup strategy for better performance.

Standards:

  • ISO-IEC-25010-Performance-Efficiency-Resource-Utilization
  • Optimization-Pattern-Array-Processing
  • React-Performance-Optimization
State Synchronization Gap

Control chart state synchronization only triggers on controlChart changes but ignores metricType changes. If metricType changes to a non-control-chart metric, the showControlChart state may retain incorrect values from previous metric types, causing UI state inconsistency.

Standards:

  • Logic-Verification-State-Management
  • Business-Rule-UI-Consistency
  • Algorithm-Correctness-Dependency-Logic
Magic Number Array

Hardcoded array of metric type constants embedded in component logic reduces maintainability. Magic strings should be extracted to named constants or configuration objects. Consider creating a MetricType enum or constants file to centralize metric definitions.

Standards:

  • Clean-Code-Meaningful-Names
  • Refactoring-Replace-Magic-Number

Comment on lines +126 to +139
data: data?.data?.map((item: any) => ({
...item,
y:
data?.chartMetadata?.chartKey === 'CUSTOM'
? Number(item.y)
: Number(item.y) / 60,
clusterP90ToP95: Number(item.clusterP90ToP95) / 60 || 0,
clusterP95ToP100: Number(item.clusterP95ToP100) / 60 || 0,
clusterP0ToP5: Number(item.clusterP0ToP5) / 60 || 0,
clusterP5ToP10: Number(item.clusterP5ToP10) / 60 || 0,
})),

return convertedItem;
}),
Copy link

Choose a reason for hiding this comment

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

Incomplete Data Mapping

Data transformation logic contains incomplete mapping with orphaned return statement on line 138. The convertedItem variable is undefined causing runtime TypeError when processing chart data. This results in chart rendering failures and potential application crashes during metric visualization.

       data: data?.data?.map((item: any) => {
         const convertedItem = {
           ...item,
           y:
             data?.chartMetadata?.chartKey === 'CUSTOM'
               ? Number(item.y)
               : Number(item.y) / 60,
           clusterP90ToP95: Number(item.clusterP90ToP95) / 60 || 0,
           clusterP95ToP100: Number(item.clusterP95ToP100) / 60 || 0,
           clusterP0ToP5: Number(item.clusterP0ToP5) / 60 || 0,
           clusterP5ToP10: Number(item.clusterP5ToP10) / 60 || 0,
         };
         return convertedItem;
       }),
Commitable Suggestion
Suggested change
data: data?.data?.map((item: any) => ({
...item,
y:
data?.chartMetadata?.chartKey === 'CUSTOM'
? Number(item.y)
: Number(item.y) / 60,
clusterP90ToP95: Number(item.clusterP90ToP95) / 60 || 0,
clusterP95ToP100: Number(item.clusterP95ToP100) / 60 || 0,
clusterP0ToP5: Number(item.clusterP0ToP5) / 60 || 0,
clusterP5ToP10: Number(item.clusterP5ToP10) / 60 || 0,
})),
return convertedItem;
}),
data: data?.data?.map((item: any) => {
const convertedItem = {
...item,
y:
data?.chartMetadata?.chartKey === 'CUSTOM'
? Number(item.y)
: Number(item.y) / 60,
clusterP90ToP95: Number(item.clusterP90ToP95) / 60 || 0,
clusterP95ToP100: Number(item.clusterP95ToP100) / 60 || 0,
clusterP0ToP5: Number(item.clusterP0ToP5) / 60 || 0,
clusterP5ToP10: Number(item.clusterP5ToP10) / 60 || 0,
};
return convertedItem;
}),
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness

filterBy.value === 'repo' ? '' : selectedTeam.value,
selected?.sprintId,
project || '',
filterBy.value === 'team' ? [] : repos.map((el: any) => el.value),
Copy link

Choose a reason for hiding this comment

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

SQL Injection Risk

User-controlled repository values passed directly to GraphQL query without validation. Attackers could inject malicious repository names to access unauthorized data or cause query manipulation. Input validation and sanitization required for repository selection parameters.

Standards
  • CWE-89
  • OWASP-A03
  • NIST-SSDF-PW.1

Comment on lines +24 to +26
const [searchParams] = useSearchParams();
const metricType = searchParams.get('type');
const project = searchParams.get('project');
Copy link

Choose a reason for hiding this comment

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

XSS via URL Parameters

URL parameters extracted without sanitization and used throughout component state. Malicious URL parameters could inject script content through React state management leading to DOM-based XSS. Implement input validation and encoding for all URL parameter usage.

Standards
  • CWE-79
  • OWASP-A03
  • CWE-20

Comment on lines +80 to +82
const handelReloadClick = async () => {
await refetch();
};
Copy link

Choose a reason for hiding this comment

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

Async Operation Risk

Async refetch operation lacks error handling for network failures or API errors. Unhandled promise rejections during data reload cause silent failures without user feedback. Users experience broken reload functionality with no indication of failure state or recovery options.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • SRE-Error-Handling

Comment on lines +110 to +118
...data,
average:
data?.average && data?.chartMetadata?.dataType == 'INTEGER'
? data?.average
: data?.average && data?.chartMetadata?.dataType == 'TIME'
? splitTime(parseInt(data?.average) / 60)
: data?.average && data?.chartMetadata?.dataType == 'PERCENTAGE'
? data?.average
: data?.average && splitTime(parseInt(data?.average) / 60),
Copy link

Choose a reason for hiding this comment

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

Conditional Logic Inconsistency

Average calculation logic uses loose equality (==) instead of strict equality (===) for dataType comparison, potentially causing type coercion issues. The final fallback condition assumes time conversion for all unmatched dataTypes, which may incorrectly process non-time numeric data types.

Standards
  • Algorithm-Correctness-Type-Safety
  • Business-Rule-Data-Validation
  • Logic-Verification-Conditional-Logic

Comment on lines +147 to +148
useEffect(() => {
if (typeof selected !== 'undefined') {
Copy link

Choose a reason for hiding this comment

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

Long useEffect Chain

Multiple useEffect hooks with complex dependency arrays and nested conditionals create difficult-to-track side effects. Effect logic spans 73 lines with intricate state synchronization. Extract effect logic into custom hooks with clear responsibilities to improve maintainability and testing.

Standards
  • Clean-Code-Functions
  • Design-Pattern-Custom-Hook
  • Refactoring-Extract-Method

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants