Merge pull request #3518 from backstage/ryanv/enable-unlabeled-dataflow-skus

enable sku breakdowns for unlabeled entities
This commit is contained in:
Ryan Vazquez
2020-12-01 15:52:54 -05:00
committed by GitHub
6 changed files with 64 additions and 50 deletions
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cost-insights': patch
---
enable SKU breakdown for unlabeled entities
@@ -176,7 +176,7 @@ export const ProductEntityDialog = ({
<Table
columns={columns}
data={rowData}
title={entity.id || 'Unknown'}
title={entity.id || 'Unlabeled'}
subtitle="Resource breakdown"
options={{
paging: false,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { useEffect, useState } from 'react';
import React, { useMemo, useState } from 'react';
import {
ContentRenderer,
TooltipProps as RechartsTooltipProps,
@@ -33,14 +33,15 @@ import {
BarChartLegendOptions,
} from '../BarChart';
import { pluralOf } from '../../utils/grammar';
import { findAlways, notEmpty } from '../../utils/assert';
import { findAlways, notEmpty, isUndefined } from '../../utils/assert';
import { formatPeriod, formatPercent } from '../../utils/formatters';
import {
titleOf,
tooltipItemOf,
resourceOf,
isInvalid,
isActiveLabel,
isLabeled,
isUnlabeled,
} from '../../utils/graphs';
import {
useProductInsightsChartStyles as useStyles,
@@ -61,10 +62,14 @@ export const ProductInsightsChart = ({
}: ProductInsightsChartProps) => {
const classes = useStyles();
const layoutClasses = useLayoutStyles();
const [isOpen, setOpen] = useState(false);
const [isClickable, setClickable] = useState(true);
const [selectLabel, setSelected] = useState<Maybe<string>>(null);
const [activeLabel, setActive] = useState<Maybe<string>>(null);
const [activeLabel, setActive] = useState<Maybe<string>>();
const [selectLabel, setSelected] = useState<Maybe<string>>();
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 && (
<ProductEntityDialog
open={isOpen}
onClose={() => setSelected(null)}
open={isSelected}
onClose={() => setSelected(undefined)}
entity={entity.entities.find(e => e.id === selectLabel)}
options={options}
/>
+9 -1
View File
@@ -17,7 +17,15 @@
export function notEmpty<TValue>(
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
+6 -2
View File
@@ -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 === '';
};
+20 -1
View File
@@ -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',