Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 286 additions & 0 deletions index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
import { Box, Button } from '@chakra-ui/react';
import { useCallback, useEffect, useState } from 'react';
import BottomSection from './BottomSection';
import TopSection from './TopSection';
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import { useDetailChartsGql } from '../graphql/index';
import { ISelect } from '../../formComponents/customSelect';
import { durationData, splitTime } from '../../shared/utils';
import Breadcrumbs from '../../appBreadcrumb';
import useQueryState from '../../hooks/useQueryState';
import { removeEmptyQueryParams } from '../../hooks/queryString';
import { useDashboardStore } from '../../hooks/useDashboardStore';
import { SlideInfoDrawer } from '../../infoSlidebar/SlideInfoDrawer';
import {
controlChartKeys,
getViewForFilter,
} from '../helpers/metricCard.utils';

interface metricDetailProps {
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 +19 to +23
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.

const [searchParams] = useSearchParams();
const metricType = searchParams.get('type');
const project = searchParams.get('project');
Comment on lines +24 to +26
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

const location = useLocation();
const navigate = useNavigate();

const [startDate, setStartDate] = useQueryState('start');
const [endDate, setEndDate] = useQueryState('end');
const [duration, setDuration] = useQueryState('duration');
const [sprintId, setSprintId] = useQueryState('sprintId');
const [sprintName, setSprintName] = useQueryState('sprint');
const [team, setTeam] = useQueryState('team');
const [view, setView] = useQueryState('view');
const [breakdownBy, setBreakdownBy] = useQueryState('breakdown');
const [showControlChart, setShowControlChart] =
useQueryState('showControlChart');
const { selectedTeam, selected, setSelectedTeam, setSelected } =
useDashboardStore();

const [openDrawer, setOpenDrawer] = useState(false);

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

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),
    []
  );


const [breakdown, setBreakdown] = useState<ISelect>({
value: '',
label: '-',
});

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[]>([]);

const [filterBy, setFilterBy] = useState<{
value: string;
label: string;
}>({ value: 'team', label: 'Team' });
const [controlChart, setControlChart] = useState(false);

const { data, isLoading, isFetching, refetch } = useDetailChartsGql(
selected.startDate,
selected.endDate,
metricType as string,
breakdown.value,
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

viewToggle.toUpperCase(),
getViewForFilter(metricType, ''),
undefined,
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 () => {

await refetch();
};
Comment on lines +80 to +82
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


useEffect(() => {
if (selected.duration === 'Sprint') {
setViewToggle('Day');
} else {
setViewToggle('Week');
}
}, [selected.duration]);

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)
Comment on lines +92 to +108

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 = [...].

? {
...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),
Comment on lines +112 to +118
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 +110 to +118
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

chartMetadata: {
...data.chartMetadata,
ylabel:
data?.chartMetadata?.chartKey === 'CUSTOM'
? data?.chartMetadata.ylabel
: 'Hour',
},
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,
Comment on lines +132 to +135
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.

})),

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

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.

Comment on lines +138 to +139
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.

Comment on lines +126 to +139
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

keys: data?.keys?.map((key: any) =>
key.name === 'Minutes' ? { ...key, name: 'Hours' } : key
),
refLines: data?.refLines,
}
: data;

useEffect(() => {
if (typeof selected !== 'undefined') {
Comment on lines +147 to +148
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

selected.startDate && setStartDate(selected.startDate);
selected.endDate && setEndDate(selected.endDate);
selected.duration && setDuration(selected.duration);
selected?.sprintId === ''
? removeEmptyQueryParams({ sprintId: '' })
: setSprintId(selected?.sprintId);
selected?.sprintName === ''
? removeEmptyQueryParams({ sprint: '' })
: setSprintName(selected?.sprintName);
}

if (typeof selectedTeam !== 'undefined') {
selectedTeam.value === ''
? removeEmptyQueryParams({ team: '' })
: setTeam(selectedTeam.value);
}

if (typeof viewToggle !== 'undefined') {
viewToggle && setView(viewToggle);
}

if (typeof breakdown.value !== 'undefined') {
breakdown.value === ''
? removeEmptyQueryParams({ breakdown: '' })
: setBreakdownBy(breakdown.value);
}
}, [selected, selectedTeam.value, viewToggle, breakdown.value]);

useEffect(() => {
team &&
setSelectedTeam({
value: team === 'Organisation' ? '' : team,
label: team,
});

duration || selected.duration
? setSelected(
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]
Comment on lines +186 to +211

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.

)
: 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.


breakdownBy && setBreakdown({ value: breakdownBy, label: breakdownBy });

view && setViewToggle(view);

showControlChart && setControlChart(showControlChart);
}, []);

useEffect(() => {
if (controlChartKeys.includes(metricType as string)) {
setShowControlChart(controlChart);
}
}, [controlChart]);

return (
<Box
display={'flex'}
flexDirection={'column'}
width={'100%'}
minHeight={'100%'}
>
{location?.pathname !== '/custom-dashboard' ? (
<Breadcrumbs />
) : (
<Button
onClick={() => navigate(-1)}
size="sm"
variant="link"
w={'fit-content'}
>
Back
</Button>
)}
<Box mt={1}>
<TopSection
data={modifiedData}
isLoading={isLoading}
setOpenDrawer={setOpenDrawer}
openDrawer={openDrawer}
/>
<BottomSection
breakdown={breakdown}
setBreakdown={setBreakdown}
selected={selected}
setSelected={setSelected}
selectedTeam={selectedTeam}
setSelectedTeam={setSelectedTeam}
isLoading={isLoading}
isFetching={isFetching}
data={modifiedData}
viewToggle={viewToggle}
setViewToggle={setViewToggle}
repos={repos}
setRepos={setRepos}
filterBy={filterBy}
setFilterBy={setFilterBy}
handelReloadClick={handelReloadClick}
controlChart={controlChart}
setControlChart={setControlChart}
/>
</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}

openDrawer={openDrawer}
title={data?.chartMetadata?.chartTitle}
chartKey={data?.chartMetadata?.chartKey}
item={data}
/>
)}
</Box>
);
};