diff --git a/.changeset/cost-insights-calm-bikes-prove.md b/.changeset/cost-insights-calm-bikes-prove.md
new file mode 100644
index 0000000000..179350fcd9
--- /dev/null
+++ b/.changeset/cost-insights-calm-bikes-prove.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-cost-insights': patch
+---
+
+enable SKU breakdown for unlabeled entities
diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx
index 4cccd638cb..c01ab67ecc 100644
--- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx
+++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductEntityDialog.tsx
@@ -176,7 +176,7 @@ export const ProductEntityDialog = ({
{
const classes = useStyles();
const layoutClasses = useLayoutStyles();
- const [isOpen, setOpen] = useState(false);
- const [isClickable, setClickable] = useState(true);
- const [selectLabel, setSelected] = useState>(null);
- const [activeLabel, setActive] = useState>(null);
+ const [activeLabel, setActive] = useState>();
+ const [selectLabel, setSelected] = useState>();
+ const isSelected = useMemo(() => !isUndefined(selectLabel), [selectLabel]);
+ const isClickable = useMemo(() => {
+ const skus =
+ entity.entities.find(e => e.id === activeLabel)?.entities ?? [];
+ return skus.length > 0;
+ }, [entity, activeLabel]);
const legendTitle = `Cost ${entity.change.ratio <= 0 ? 'Savings' : 'Growth'}`;
const costStart = entity.aggregation[0];
@@ -76,52 +81,25 @@ export const ProductInsightsChart = ({
currentName: formatPeriod(duration, billingDate, true),
};
- useEffect(() => {
- function toggleModal() {
- if (selectLabel) {
- setOpen(true);
- } else {
- setOpen(false);
- }
- }
-
- toggleModal();
- }, [selectLabel]);
-
- useEffect(() => {
- // disable click if an entity is unlabeled or if it does not have any skus
- function toggleClickableEntity() {
- if (activeLabel) {
- const hasSkus = entity.entities.find(e => e.id === activeLabel)
- ?.entities.length;
- if (hasSkus) {
- setClickable(true);
- } else {
- setClickable(false);
- }
- } else {
- setClickable(false);
- }
- }
-
- toggleClickableEntity();
- }, [activeLabel, entity]);
-
const onMouseMove: RechartsFunction = (
data: Record<'activeLabel', string | undefined>,
) => {
- if (isActiveLabel(data)) {
+ if (isLabeled(data)) {
setActive(data.activeLabel!);
- } else {
+ } else if (isUnlabeled(data)) {
setActive(null);
+ } else {
+ setActive(undefined);
}
};
const onClick: RechartsFunction = (data: Record<'activeLabel', string>) => {
- if (isActiveLabel(data)) {
+ if (isLabeled(data)) {
setSelected(data.activeLabel);
- } else {
+ } else if (isUnlabeled(data)) {
setSelected(null);
+ } else {
+ setSelected(undefined);
}
};
@@ -213,10 +191,10 @@ export const ProductInsightsChart = ({
options={options}
{...barChartProps}
/>
- {selectLabel && entity.entities.length && (
+ {isSelected && entity.entities.length && (
setSelected(null)}
+ open={isSelected}
+ onClose={() => setSelected(undefined)}
entity={entity.entities.find(e => e.id === selectLabel)}
options={options}
/>
diff --git a/plugins/cost-insights/src/utils/assert.ts b/plugins/cost-insights/src/utils/assert.ts
index 8f7caef0fa..19ddabe574 100644
--- a/plugins/cost-insights/src/utils/assert.ts
+++ b/plugins/cost-insights/src/utils/assert.ts
@@ -17,7 +17,15 @@
export function notEmpty(
value: TValue | null | undefined,
): value is TValue {
- return value !== null && value !== undefined;
+ return !isNull(value) && !isUndefined(value);
+}
+
+export function isUndefined(value: any): boolean {
+ return value === undefined;
+}
+
+export function isNull(value: any): boolean {
+ return value === null;
}
// Utility for exhaustiveness checking in switch statements
diff --git a/plugins/cost-insights/src/utils/graphs.ts b/plugins/cost-insights/src/utils/graphs.ts
index ac06e20587..bc82b63f89 100644
--- a/plugins/cost-insights/src/utils/graphs.ts
+++ b/plugins/cost-insights/src/utils/graphs.ts
@@ -72,8 +72,12 @@ export const isInvalid = ({ label, payload }: TooltipProps) => {
return label === undefined || !payload || !payload.length;
};
-export const isActiveLabel = (
+export const isLabeled = (data?: Record<'activeLabel', string | undefined>) => {
+ return data?.activeLabel && data?.activeLabel !== '';
+};
+
+export const isUnlabeled = (
data?: Record<'activeLabel', string | undefined>,
) => {
- return data?.activeLabel && data.activeLabel !== '';
+ return data?.activeLabel === '';
};
diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts
index f188bd7a57..98eab19b41 100644
--- a/plugins/cost-insights/src/utils/mockData.ts
+++ b/plugins/cost-insights/src/utils/mockData.ts
@@ -563,7 +563,26 @@ export const SampleCloudDataflowInsights: Entity = {
ratio: 0.2,
amount: 2_000,
},
- entities: [],
+ entities: [
+ {
+ id: 'Sample SKU A',
+ aggregation: [3_000, 4_000],
+ change: {
+ ratio: 0.333333,
+ amount: 1_000,
+ },
+ entities: [],
+ },
+ {
+ id: 'Sample SKU B',
+ aggregation: [7_000, 8_000],
+ change: {
+ ratio: 0.14285714,
+ amount: 1_000,
+ },
+ entities: [],
+ },
+ ],
},
{
id: 'entity-a',