feat: make engineer threshold configurable
Signed-off-by: Chris Langhout <clanghout@bol.com>
This commit is contained in:
@@ -373,6 +373,7 @@ auth:
|
||||
development: {}
|
||||
costInsights:
|
||||
engineerCost: 200000
|
||||
engineerThreshold: 0.5
|
||||
products:
|
||||
computeEngine:
|
||||
name: Compute Engine
|
||||
|
||||
@@ -222,6 +222,19 @@ costInsights:
|
||||
rate: 3.5
|
||||
```
|
||||
|
||||
### costChangeNegligibleThreshold (Optional; default 0.5)
|
||||
|
||||
This threshold determines whether to show 'Negligible', or a percentage with a fraction of 'engineers' for cost savings or cost excess on top of the charts.
|
||||
A threshold of 0.5 means that `Negligible` is shown when the difference in costs is lower than that fraction of engineers in that time frame,
|
||||
and show `XX% or ~N engineers` when it's above the threshold.
|
||||
|
||||
```yaml
|
||||
## ./app-config.yaml
|
||||
costInsights:
|
||||
engineerCost: 200000
|
||||
engineerThreshold: 0.5
|
||||
```
|
||||
|
||||
## Alerts
|
||||
|
||||
The CostInsightsApi `getAlerts` method may return any type of alert or recommendation (called collectively "Action Items" in Cost Insights) that implements the [Alert type](https://github.com/backstage/backstage/blob/master/plugins/cost-insights/src/types/Alert.ts). This allows you to deliver any alerts or recommendations specific to your infrastructure or company migrations.
|
||||
|
||||
@@ -237,6 +237,7 @@ export type ConfigContextProps = {
|
||||
products: Product[];
|
||||
icons: Icon[];
|
||||
engineerCost: number;
|
||||
engineerThreshold: number;
|
||||
currencies: Currency[];
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ import classnames from 'classnames';
|
||||
import {
|
||||
CurrencyType,
|
||||
Duration,
|
||||
EngineerThreshold,
|
||||
GrowthType,
|
||||
} from '../../types';
|
||||
import { ChangeStatistic } from '@backstage/plugin-cost-insights-common';
|
||||
@@ -42,7 +41,7 @@ export const CostGrowth = (props: CostGrowthProps) => {
|
||||
const { change, duration } = props;
|
||||
|
||||
const styles = useStyles();
|
||||
const { engineerCost } = useConfig();
|
||||
const { engineerCost, engineerThreshold } = useConfig();
|
||||
const [currency] = useCurrency();
|
||||
|
||||
// Only display costs in absolute values
|
||||
@@ -55,7 +54,7 @@ export const CostGrowth = (props: CostGrowthProps) => {
|
||||
|
||||
// If a ratio cannot be calculated, don't format.
|
||||
const growth = notEmpty(change.ratio)
|
||||
? growthOf({ ratio: change.ratio, amount: engineers })
|
||||
? growthOf({ ratio: change.ratio, amount: engineers }, engineerThreshold)
|
||||
: null;
|
||||
// Determine if growth is significant enough to highlight
|
||||
const classes = classnames({
|
||||
@@ -63,7 +62,7 @@ export const CostGrowth = (props: CostGrowthProps) => {
|
||||
[styles.savings]: growth === GrowthType.Savings,
|
||||
});
|
||||
|
||||
if (engineers < EngineerThreshold) {
|
||||
if (engineers < engineerThreshold) {
|
||||
return <span className={classes}>Negligible</span>;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import { growthOf } from '../../utils/change';
|
||||
import { GrowthType } from '../../types';
|
||||
import { useCostGrowthStyles as useStyles } from '../../utils/styles';
|
||||
import { ChangeStatistic, Maybe } from '@backstage/plugin-cost-insights-common';
|
||||
import { useConfig } from '../../hooks';
|
||||
|
||||
/** @public */
|
||||
export type CostGrowthIndicatorProps = TypographyProps & {
|
||||
@@ -35,10 +36,11 @@ export type CostGrowthIndicatorProps = TypographyProps & {
|
||||
|
||||
/** @public */
|
||||
export const CostGrowthIndicator = (props: CostGrowthIndicatorProps) => {
|
||||
const { engineerThreshold } = useConfig();
|
||||
const { change, formatter, className, ...extraProps } = props;
|
||||
|
||||
const classes = useStyles();
|
||||
const growth = growthOf(change);
|
||||
const growth = growthOf(change, engineerThreshold);
|
||||
|
||||
const classNames = classnames(classes.indicator, className, {
|
||||
[classes.excess]: growth === GrowthType.Excess,
|
||||
|
||||
@@ -70,6 +70,7 @@ export type ConfigContextProps = {
|
||||
products: Product[];
|
||||
icons: Icon[];
|
||||
engineerCost: number;
|
||||
engineerThreshold: number;
|
||||
currencies: Currency[];
|
||||
};
|
||||
|
||||
@@ -83,6 +84,7 @@ const defaultState: ConfigContextProps = {
|
||||
products: [],
|
||||
icons: [],
|
||||
engineerCost: 0,
|
||||
engineerThreshold: 0.5,
|
||||
currencies: defaultCurrencies,
|
||||
};
|
||||
|
||||
@@ -183,11 +185,19 @@ export const ConfigProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
return c.getNumber('costInsights.engineerCost');
|
||||
}
|
||||
|
||||
function getEngineerThreshold(): number {
|
||||
return (
|
||||
c.getOptionalNumber('costInsights.engineerThreshold') ??
|
||||
defaultState.engineerThreshold
|
||||
);
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
const baseCurrency = getBaseCurrency();
|
||||
const products = getProducts();
|
||||
const metrics = getMetrics();
|
||||
const engineerCost = getEngineerCost();
|
||||
const engineerThreshold = getEngineerThreshold();
|
||||
const icons = getIcons();
|
||||
const currencies = getCurrencies();
|
||||
|
||||
@@ -200,6 +210,7 @@ export const ConfigProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
metrics,
|
||||
products,
|
||||
engineerCost,
|
||||
engineerThreshold,
|
||||
icons,
|
||||
currencies,
|
||||
}));
|
||||
|
||||
@@ -95,6 +95,7 @@ export const MockConfigProvider = (props: MockConfigProviderProps) => {
|
||||
products: [],
|
||||
icons: [],
|
||||
engineerCost: 0,
|
||||
engineerThreshold: 0.5,
|
||||
currencies: [],
|
||||
};
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ describe.each`
|
||||
expected: GrowthType;
|
||||
}) => {
|
||||
it(`should display ${GrowthMap[expected]}`, () => {
|
||||
expect(growthOf({ ratio, amount })).toBe(expected);
|
||||
expect(growthOf({ ratio, amount }, EngineerThreshold)).toBe(expected);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
Cost,
|
||||
ChangeStatistic,
|
||||
ChangeThreshold,
|
||||
EngineerThreshold,
|
||||
GrowthType,
|
||||
MetricData,
|
||||
Duration,
|
||||
@@ -29,8 +28,11 @@ import { inclusiveStartDateOf } from './duration';
|
||||
import { notEmpty } from './assert';
|
||||
|
||||
// Used for displaying status colors
|
||||
export function growthOf(change: ChangeStatistic): GrowthType {
|
||||
const exceedsEngineerThreshold = Math.abs(change.amount) >= EngineerThreshold;
|
||||
export function growthOf(
|
||||
change: ChangeStatistic,
|
||||
engineerThreshold: number,
|
||||
): GrowthType {
|
||||
const exceedsEngineerThreshold = Math.abs(change.amount) >= engineerThreshold;
|
||||
|
||||
if (notEmpty(change.ratio)) {
|
||||
if (exceedsEngineerThreshold && change.ratio >= ChangeThreshold.upper) {
|
||||
@@ -41,9 +43,12 @@ export function growthOf(change: ChangeStatistic): GrowthType {
|
||||
return GrowthType.Savings;
|
||||
}
|
||||
} else {
|
||||
if (exceedsEngineerThreshold && change.amount > 0) return GrowthType.Excess;
|
||||
if (exceedsEngineerThreshold && change.amount < 0)
|
||||
if (exceedsEngineerThreshold && change.amount > 0) {
|
||||
return GrowthType.Excess;
|
||||
}
|
||||
if (exceedsEngineerThreshold && change.amount < 0) {
|
||||
return GrowthType.Savings;
|
||||
}
|
||||
}
|
||||
|
||||
return GrowthType.Negligible;
|
||||
|
||||
Reference in New Issue
Block a user