-
Notifications
You must be signed in to change notification settings - Fork 4
Add MetricDetails component with chart functionality #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add MetricDetails component with chart functionality #32
Conversation
|
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 · |
There was a problem hiding this 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.
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds Changes
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Summary of ChangesHello @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 Highlights
Using Gemini Code AssistThe 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
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 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
|
|
CodeAnt AI finished reviewing your PR. |
There was a problem hiding this 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.
| return convertedItem; | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const [periodOptions, setPeriodOptions] = useState<any>(durationData); | ||
|
|
||
| const [viewToggle, setViewToggle] = useState<string>('Week'); | ||
| const [repos, setRepos] = useState<string[]>([]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| initialTeam?: string; | ||
| } | ||
|
|
||
| export const MetricDetails = ({ initialTeam }: metricDetailProps) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const handleDrawerClose = useCallback( | ||
| () => setOpenDrawer(false), | ||
| [openDrawer] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
|
/refacto-visz |
|
Refacto is reviewing this PR. Please wait for the review comments to be posted. |
There was a problem hiding this 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
handleDrawerClosecallback includesopenDrawerin 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
📒 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.
| interface metricDetailProps { | ||
| initialTeam?: string; | ||
| } | ||
|
|
||
| export const MetricDetails = ({ initialTeam }: metricDetailProps) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.
| 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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| clusterP90ToP95: Number(item.clusterP90ToP95) / 60 || 0, | ||
| clusterP95ToP100: Number(item.clusterP95ToP100) / 60 || 0, | ||
| clusterP0ToP5: Number(item.clusterP0ToP5) / 60 || 0, | ||
| clusterP5ToP10: Number(item.clusterP5ToP10) / 60 || 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -60Length 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)" -iLength of output: 770
🏁 Script executed:
#!/bin/bash
# Find what happens after the data transformation (after line 136)
rg -nP --type ts -A20 'clusterP5ToP10:' | head -40Length of output: 993
🏁 Script executed:
#!/bin/bash
# Find where convertedItem or the return value is consumed
rg -nP --type ts -B10 "return convertedItem" | head -30Length 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 -5Length 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 -10Length 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 -40Length 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.
| return convertedItem; | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
Code Review: MetricDetails Component - Critical Data Mapping Issues👍 Well Done
📁 Selected files for review (1)
🎯 Custom Instructions
📝 Additional Comments
|
| 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; | ||
| }), |
There was a problem hiding this comment.
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
| 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), |
There was a problem hiding this comment.
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
| const [searchParams] = useSearchParams(); | ||
| const metricType = searchParams.get('type'); | ||
| const project = searchParams.get('project'); |
There was a problem hiding this comment.
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
| const handelReloadClick = async () => { | ||
| await refetch(); | ||
| }; |
There was a problem hiding this comment.
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
| ...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), |
There was a problem hiding this comment.
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
| useEffect(() => { | ||
| if (typeof selected !== 'undefined') { |
There was a problem hiding this comment.
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
CodeAnt-AI Description
Display cycle-time metrics in hours and preserve control-chart state in MetricDetails
What Changed
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:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
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:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
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