Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
201f0f2
Implement drilldown functionality for namespace and resource costs
CrazyCapislav Nov 12, 2025
67a3c9d
Update recharts to 3.4.1 and fix breaking changes
CrazyCapislav Nov 12, 2025
11b0d02
Fix drilldown: disable click when no next level exists
CrazyCapislav Nov 12, 2025
3166177
Fix filter parsing: use '+' instead of encodeURIComponent to avoid do…
CrazyCapislav Nov 12, 2025
d995809
Fix filter parsing: escape quotes in values and validate filter values
CrazyCapislav Nov 12, 2025
483e292
Fix filter property: use controllerName instead of controller for bac…
CrazyCapislav Nov 12, 2025
de4b0d2
Remove dead code: commented out isAnimationActive prop
CrazyCapislav Nov 13, 2025
7b43cc6
Add URL-based filter management for drilldown functionality
CrazyCapislav Nov 15, 2025
7765020
Require explicit flag to enable mock data usage
CrazyCapislav Nov 15, 2025
d443ad6
Merge branch 'main' into feature/drilldown-namespace-2942
CrazyCapislav Nov 15, 2025
e8a6506
Restore ResponsiveContainer wrappers for charts
CrazyCapislav Nov 25, 2025
7381f7d
Revert "Restore ResponsiveContainer wrappers for charts"
CrazyCapislav Nov 25, 2025
88ebf55
Normalize controller filters during drilldown
CrazyCapislav Nov 25, 2025
f9fbfa5
Fix indentation for responsive, height, and width props in BarChart
CrazyCapislav Dec 1, 2025
4fa48eb
Remove redundant isContainer check - hasNextLevel already implies !is…
CrazyCapislav Dec 3, 2025
243241d
Merge branch 'main' into feature/drilldown-namespace-2942
CrazyCapislav Dec 3, 2025
00ee55d
Simplify filters state management - use URL as single source of truth
CrazyCapislav Dec 3, 2025
2f2330c
Move getMockData to separate file for better code organization
CrazyCapislav Dec 3, 2025
82f133d
Regenerate package-lock.json to remove unused dependencies
CrazyCapislav Dec 3, 2025
58421c7
Add breadcrumb navigation to show applied filters in drilldown
CrazyCapislav Dec 3, 2025
078b6a0
Fix filter hierarchy mismatch between FilterBreadcrumb and Allocation…
CrazyCapislav Dec 3, 2025
ad963da
Fix React error #185: optimize useEffect dependencies and memoize fil…
CrazyCapislav Dec 3, 2025
461dceb
Merge branch 'main' into feature/drilldown-namespace-2942
CrazyCapislav Dec 4, 2025
8472e8d
Regenerate package-lock.json
CrazyCapislav Dec 4, 2025
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
7,930 changes: 3,965 additions & 3,965 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 20 additions & 20 deletions src/components/AllocationChart/RangeChart.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
import { primary, greyscale, browns } from "../../constants/colors";
import { toCurrency } from "../../util";
Expand Down Expand Up @@ -144,25 +143,26 @@ const RangeChart = ({ data, currency, height }) => {
};

return (
<ResponsiveContainer width="100%" height={height}>
<BarChart
data={barData}
margin={{ top: 30, right: 30, left: 30, bottom: 12 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="start" />
<YAxis />
<Tooltip content={<CustomTooltip />} />
{barLabels.map((barLabel, i) => (
<Bar
key={i}
dataKey={barLabel.dataKey}
stackId="a"
fill={barLabel.fill}
/>
))}
</BarChart>
</ResponsiveContainer>
<BarChart
data={barData}
margin={{ top: 30, right: 30, left: 30, bottom: 12 }}
responsive
width="100%"
height={height}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="start" />
<YAxis />
<Tooltip content={<CustomTooltip />} />
{barLabels.map((barLabel, i) => (
<Bar
key={i}
dataKey={barLabel.dataKey}
stackId="a"
fill={barLabel.fill}
/>
))}
</BarChart>
);
};

Expand Down
48 changes: 24 additions & 24 deletions src/components/AllocationChart/SummaryChart.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { ResponsiveContainer, PieChart, Pie, Cell } from "recharts";
import { PieChart, Pie, Cell } from "recharts";
import { primary, greyscale, browns } from "../../constants/colors";
import { toCurrency } from "../../util";

Expand Down Expand Up @@ -75,29 +75,29 @@ const SummaryChart = ({ top, other, idle, currency, height }) => {
};

return (
<ResponsiveContainer width="100%" height={height}>
<PieChart>
<Pie
data={pieData}
dataKey="value"
nameKey="name"
label={renderLabel}
labelLine
// niko: if tooltips error, try disabling animation
// isAnimationActive={false}
animationDuration={400}
cy="90%"
outerRadius="140%"
innerRadius="60%"
startAngle={180}
endAngle={0}
>
{pieData.map((datum, i) => (
<Cell key={i} fill={datum.fill} />
))}
</Pie>
</PieChart>
</ResponsiveContainer>
<PieChart
responsive
width="100%"
height={height}
>
<Pie
data={pieData}
dataKey="value"
nameKey="name"
label={renderLabel}
labelLine
animationDuration={400}
cy="90%"
outerRadius="140%"
innerRadius="60%"
startAngle={180}
endAngle={0}
>
{pieData.map((datum, i) => (
<Cell key={i} fill={datum.fill} />
))}
</Pie>
</PieChart>
);
};

Expand Down
91 changes: 91 additions & 0 deletions src/components/FilterBreadcrumb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as React from "react";
import Breadcrumbs from "@mui/material/Breadcrumbs";
import Link from "@mui/material/Link";
import Typography from "@mui/material/Typography";
import NavigateNextIcon from "@mui/icons-material/NavigateNext";

const FilterBreadcrumb = ({ filters, onNavigate }) => {
if (!filters || filters.length === 0) {
return null;
}

const hierarchy = ["namespace", "controllerKind", "controllerName", "pod", "container"];
const hierarchyLabels = {
namespace: "Namespace",
controllerKind: "Controller Kind",
controllerName: "Controller",
pod: "Pod",
container: "Container",
};

const breadcrumbItems = [
{
label: "All Results",
level: -1,
isClickable: true,
},
];

// Build breadcrumb items from filters
filters.forEach((filter, index) => {
const property = filter.property;
const value = filter.value;
const level = hierarchy.indexOf(property);

if (level >= 0) {
breadcrumbItems.push({
label: value,
level: index,
property: property,
isClickable: index < filters.length - 1, // Last item is not clickable
});
}
});

const handleClick = (event, item) => {
event.preventDefault();
if (item.isClickable && onNavigate) {
onNavigate(item.level);
}
};

return (
<Breadcrumbs
separator={<NavigateNextIcon fontSize="small" />}
aria-label="filter breadcrumb"
sx={{ marginTop: 1, marginBottom: 1 }}
>
{breadcrumbItems.map((item, index) => {
const key = `${item.level}-${item.label}-${index}`;
if (item.isClickable) {
return (
<Link
key={key}
component="button"
variant="body2"
onClick={(e) => handleClick(e, item)}
sx={{
cursor: "pointer",
textDecoration: "none",
"&:hover": {
textDecoration: "underline",
},
}}
>
{item.label}
</Link>
);
} else {
return (
<Typography key={key} variant="body2" color="text.primary">
{item.label}
</Typography>
);
}
})}
</Breadcrumbs>
);
};

export default React.memo(FilterBreadcrumb);

29 changes: 27 additions & 2 deletions src/components/allocationReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ const AllocationReport = ({
cumulativeData,
totalData,
currency,
aggregateBy,
drilldown,
}) => {
if (allocationData.length === 0) {
return (
Expand Down Expand Up @@ -169,7 +171,30 @@ const AllocationReport = ({
efficiency = "Inf";
}

// Do not allow drill-down for idle and unallocated rows
// Do not allow drill-down for idle, unallocated, unmounted, or if no next level exists
// According to issue #2942, drilldown should work from namespace level
const drilldownHierarchy = {
namespace: "controllerKind",
controllerKind: "controller",
controller: "pod",
pod: "container",
};
const hasNextLevel = drilldownHierarchy[aggregateBy] !== undefined;
// Only allow drilldown if there's a next level (hasNextLevel implies !isContainer)
const canDrilldown = !isIdle && !isUnallocated && !isUnmounted && hasNextLevel && drilldown;

const rowProps = canDrilldown
? {
onClick: () => drilldown(row),
sx: {
cursor: "pointer",
"&:hover": {
backgroundColor: "rgba(0, 0, 0, 0.04)",
},
},
}
: {};

if (isIdle || isUnallocated || isUnmounted) {
return (
<TableRow key={key}>
Expand Down Expand Up @@ -199,7 +224,7 @@ const AllocationReport = ({
}

return (
<TableRow key={key}>
<TableRow key={key} {...rowProps}>
<TableCell align="left">{row.name}</TableCell>
<TableCell align="right">
{toCurrency(row.cpuCost, currency)}
Expand Down
52 changes: 26 additions & 26 deletions src/components/cloudCost/cloudCostChart/rangeChart.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
Cell,
} from "recharts";
import { primary, greyscale, browns } from "../../../constants/colors";
Expand Down Expand Up @@ -243,32 +242,33 @@ const RangeChart = ({ data, currency, height }) => {
});

return (
<ResponsiveContainer height={height} width={"100%"}>
<BarChart
data={orderedBars}
margin={{ top: 30, right: 35, left: 30, bottom: 45 }}
>
<CartesianGrid strokeDasharray={"3 3"} vertical={false} />
<XAxis dataKey={"key"} />
<YAxis tickFormatter={(val) => toCurrency(val, currency, 2, true)} />
<Tooltip content={<CustomTooltip />} wrapperStyle={{ zIndex: 1000 }} />
<BarChart
data={orderedBars}
margin={{ top: 30, right: 35, left: 30, bottom: 45 }}
responsive
height={height}
width="100%"
>
<CartesianGrid strokeDasharray={"3 3"} vertical={false} />
<XAxis dataKey={"key"} />
<YAxis tickFormatter={(val) => toCurrency(val, currency, 2, true)} />
<Tooltip content={<CustomTooltip />} wrapperStyle={{ zIndex: 1000 }} />

{new Array(10).fill(0).map((item, idx) => (
<Bar
dataKey={(entry) => (entry.items[idx] ? entry.items[idx][1] : null)}
stackId="x"
>
{orderedBars.map((bar) =>
bar.items[idx] ? (
<Cell fill={keyToFill[bar.items[idx][0]]} />
) : (
<Cell />
),
)}
</Bar>
))}
</BarChart>
</ResponsiveContainer>
{new Array(10).fill(0).map((item, idx) => (
<Bar
dataKey={(entry) => (entry.items[idx] ? entry.items[idx][1] : null)}
stackId="x"
>
{orderedBars.map((bar) =>
bar.items[idx] ? (
<Cell fill={keyToFill[bar.items[idx][0]]} />
) : (
<Cell />
),
)}
</Bar>
))}
</BarChart>
);
};

Expand Down
11 changes: 4 additions & 7 deletions src/components/cloudCost/cloudCostDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import Warnings from "../../components/Warnings";
import CircularProgress from "@mui/material/CircularProgress";

import {
ResponsiveContainer,
CartesianGrid,
Legend,
XAxis,
Expand Down Expand Up @@ -140,11 +139,6 @@ const CloudCostDetails = ({
)}
{data && (
<div style={{ display: "flex", marginTop: "2.5rem" }}>
<ResponsiveContainer
height={250}
id={"cloud-cost-drilldown"}
width={"100%"}
>
<BarChart
data={itemData}
margin={{
Expand All @@ -153,6 +147,10 @@ const CloudCostDetails = ({
left: 20,
right: 0,
}}
responsive
height={250}
width="100%"
id={"cloud-cost-drilldown"}
>
<CartesianGrid vertical={false} />
<Legend verticalAlign={"bottom"} />
Expand All @@ -165,7 +163,6 @@ const CloudCostDetails = ({
}
/>
</BarChart>
</ResponsiveContainer>
</div>
)}
</Paper>
Expand Down
6 changes: 3 additions & 3 deletions src/components/externalCosts/rangeChart.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
Cell,
} from "recharts";
import { primary, greyscale, browns } from "../../constants/colors";
Expand Down Expand Up @@ -193,10 +192,12 @@ const RangeChart = ({ data, currency, height, aggregateBy }) => {
});

return (
<ResponsiveContainer height={height} width={"100%"}>
<BarChart
data={orderedBars}
margin={{ top: 30, right: 35, left: 30, bottom: 45 }}
responsive
height={height}
width="100%"
>
<CartesianGrid strokeDasharray={"3 3"} vertical={false} />
<XAxis dataKey={"key"} />
Expand All @@ -218,7 +219,6 @@ const RangeChart = ({ data, currency, height, aggregateBy }) => {
</Bar>
))}
</BarChart>
</ResponsiveContainer>
);
};

Expand Down
Loading