Merge pull request #3536 from backstage/update-breakdown-label

Support custom labels for entity breakdowns
This commit is contained in:
brendasukh
2020-12-02 17:31:55 -05:00
committed by GitHub
6 changed files with 122 additions and 17 deletions
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cost-insights': patch
---
Add support for non-SKU breakdowns for entities in the product panels.
+3
View File
@@ -290,6 +290,9 @@ costInsights:
bigQuery:
name: BigQuery
icon: search
events:
name: Events
icon: data
metrics:
DAU:
name: Daily Active Users
@@ -29,7 +29,7 @@ function createRenderer(col: keyof RowData, classes: Record<string, string>) {
const row = rowData as RowData;
const rowStyles = classnames(classes.row, {
[classes.rowTotal]: row.id === 'total',
[classes.colFirst]: col === 'sku',
[classes.colFirst]: col === 'label',
[classes.colLast]: col === 'ratio',
});
@@ -50,7 +50,7 @@ function createRenderer(col: keyof RowData, classes: Record<string, string>) {
/>
);
default:
return <Typography className={rowStyles}>{row.sku}</Typography>;
return <Typography className={rowStyles}>{row.label}</Typography>;
}
};
}
@@ -63,7 +63,7 @@ function createSorter(field?: keyof Omit<RowData, 'id'>) {
const b = data2 as RowData;
if (a.id === 'total') return 1;
if (b.id === 'total') return 1;
if (field === 'sku') return a.sku.localeCompare(b.sku);
if (field === 'label') return a.label.localeCompare(b.label);
return field
? a[field] - b[field]
@@ -80,7 +80,7 @@ const defaultEntity: Entity = {
type RowData = {
id: string;
sku: string;
label: string;
previous: number;
current: number;
ratio: number;
@@ -93,6 +93,7 @@ type ProductEntityDialogOptions = Partial<
type ProductEntityDialogProps = {
open: boolean;
entity?: Entity;
entitiesLabel: string;
options?: ProductEntityDialogOptions;
onClose: () => void;
};
@@ -100,6 +101,7 @@ type ProductEntityDialogProps = {
export const ProductEntityDialog = ({
open,
entity = defaultEntity,
entitiesLabel,
options = {},
onClose,
}: ProductEntityDialogProps) => {
@@ -118,10 +120,12 @@ export const ProductEntityDialog = ({
const columns: TableColumn[] = [
{
field: 'sku',
title: <Typography className={firstColClasses}>SKU</Typography>,
render: createRenderer('sku', classes),
customSort: createSorter('sku'),
field: 'label',
title: (
<Typography className={firstColClasses}>{entitiesLabel}</Typography>
),
render: createRenderer('label', classes),
customSort: createSorter('label'),
width: '33.33%',
},
{
@@ -154,14 +158,14 @@ export const ProductEntityDialog = ({
const rowData: RowData[] = entity.entities
.map(e => ({
id: e.id || 'Unknown',
sku: e.id || 'Unknown',
label: e.id || 'Unknown',
previous: e.aggregation[0],
current: e.aggregation[1],
ratio: e.change.ratio,
}))
.concat({
id: 'total',
sku: 'Total',
label: 'Total',
previous: entity.aggregation[0],
current: entity.aggregation[1],
ratio: entity.change.ratio,
@@ -66,9 +66,9 @@ export const ProductInsightsChart = ({
const [selectLabel, setSelected] = useState<Maybe<string>>();
const isSelected = useMemo(() => !isUndefined(selectLabel), [selectLabel]);
const isClickable = useMemo(() => {
const skus =
const breakdownEntities =
entity.entities.find(e => e.id === activeLabel)?.entities ?? [];
return skus.length > 0;
return breakdownEntities.length > 0;
}, [entity, activeLabel]);
const legendTitle = `Cost ${entity.change.ratio <= 0 ? 'Savings' : 'Growth'}`;
@@ -122,10 +122,13 @@ export const ProductInsightsChart = ({
const activeEntity = findAlways(entity.entities, e => e.id === id);
const ratio = activeEntity.change.ratio;
const skus = activeEntity.entities;
const subtitle = `${skus.length} ${pluralOf(skus.length, 'SKU')}`;
const breakdownEntities = activeEntity.entities;
const subtitle = `${breakdownEntities.length} ${pluralOf(
breakdownEntities.length,
entity.entitiesLabel || 'SKU',
)}`;
if (skus.length) {
if (breakdownEntities.length) {
return (
<BarChartTooltip
title={title}
@@ -151,7 +154,7 @@ export const ProductInsightsChart = ({
);
}
// If an entity doesn't have any skus, there aren't any costs to break down.
// If an entity doesn't have any sub-entities, there aren't any costs to break down.
return (
<BarChartTooltip
title={title}
@@ -197,6 +200,7 @@ export const ProductInsightsChart = ({
onClose={() => setSelected(undefined)}
entity={entity.entities.find(e => e.id === selectLabel)}
options={options}
entitiesLabel={entity.entitiesLabel || 'SKU'}
/>
)}
</Box>
@@ -22,6 +22,7 @@ export interface Entity {
aggregation: [number, number];
entities: Entity[];
change: ChangeStatistic;
entitiesLabel?: string;
}
/*
+89 -1
View File
@@ -812,7 +812,7 @@ export const SampleComputeEngineInsights: Entity = {
id: 'Sample SKU A',
aggregation: [1_000, 2_000],
change: {
ratio: 0.5,
ratio: 1,
amount: 1_000,
},
entities: [],
@@ -849,6 +849,92 @@ export const SampleComputeEngineInsights: Entity = {
],
};
export const SampleEventsInsights: Entity = {
id: 'events',
aggregation: [20_000, 10_000],
change: {
ratio: -0.5,
amount: -10_000,
},
entitiesLabel: 'Product',
entities: [
{
id: 'entity-a',
aggregation: [15_000, 7_000],
change: {
ratio: -0.53333333333,
amount: -8_000,
},
entities: [
{
id: 'Sample Product A',
aggregation: [5_000, 2_000],
change: {
ratio: -0.6,
amount: -3_000,
},
entities: [],
},
{
id: 'Sample Product B',
aggregation: [7_000, 2_500],
change: {
ratio: -0.64285714285,
amount: -4_500,
},
entities: [],
},
{
id: 'Sample Product C',
aggregation: [3_000, 2_500],
change: {
ratio: -0.16666666666,
amount: -500,
},
entities: [],
},
],
},
{
id: 'entity-b',
aggregation: [5_000, 3_000],
change: {
ratio: -0.4,
amount: -2_000,
},
entities: [
{
id: 'Sample Product A',
aggregation: [2_000, 1_000],
change: {
ratio: -0.5,
amount: -1_000,
},
entities: [],
},
{
id: 'Sample Product B',
aggregation: [1_000, 1_500],
change: {
ratio: 0.5,
amount: 500,
},
entities: [],
},
{
id: 'Sample Product C',
aggregation: [2_000, 500],
change: {
ratio: -0.75,
amount: -1_500,
},
entities: [],
},
],
},
],
};
export function entityOf(product: string): Entity {
switch (product) {
case 'computeEngine':
@@ -859,6 +945,8 @@ export function entityOf(product: string): Entity {
return SampleCloudStorageInsights;
case 'bigQuery':
return SampleBigQueryInsights;
case 'events':
return SampleEventsInsights;
default:
throw new Error(
`Cannot get insights for ${product}. Make sure product matches product property in app-info.yaml`,