diff --git a/app-config.yaml b/app-config.yaml index ef133491af..dd72051189 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -373,6 +373,7 @@ auth: development: {} costInsights: engineerCost: 200000 + engineerThreshold: 0.5 products: computeEngine: name: Compute Engine diff --git a/plugins/cost-insights/README.md b/plugins/cost-insights/README.md index f65194551a..de9e2d5596 100644 --- a/plugins/cost-insights/README.md +++ b/plugins/cost-insights/README.md @@ -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. diff --git a/plugins/cost-insights/api-report.md b/plugins/cost-insights/api-report.md index 8ae49dcedc..9bc662dae1 100644 --- a/plugins/cost-insights/api-report.md +++ b/plugins/cost-insights/api-report.md @@ -237,6 +237,7 @@ export type ConfigContextProps = { products: Product[]; icons: Icon[]; engineerCost: number; + engineerThreshold: number; currencies: Currency[]; }; diff --git a/plugins/cost-insights/src/components/CostGrowth/CostGrowth.tsx b/plugins/cost-insights/src/components/CostGrowth/CostGrowth.tsx index d0a4e7901a..cc66c05d3f 100644 --- a/plugins/cost-insights/src/components/CostGrowth/CostGrowth.tsx +++ b/plugins/cost-insights/src/components/CostGrowth/CostGrowth.tsx @@ -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 Negligible; } diff --git a/plugins/cost-insights/src/components/CostGrowth/CostGrowthIndicator.tsx b/plugins/cost-insights/src/components/CostGrowth/CostGrowthIndicator.tsx index 2591fd5a47..123d1e40b7 100644 --- a/plugins/cost-insights/src/components/CostGrowth/CostGrowthIndicator.tsx +++ b/plugins/cost-insights/src/components/CostGrowth/CostGrowthIndicator.tsx @@ -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, diff --git a/plugins/cost-insights/src/hooks/useConfig.tsx b/plugins/cost-insights/src/hooks/useConfig.tsx index 6095f97d49..92b4c6c9f7 100644 --- a/plugins/cost-insights/src/hooks/useConfig.tsx +++ b/plugins/cost-insights/src/hooks/useConfig.tsx @@ -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, })); diff --git a/plugins/cost-insights/src/testUtils/providers.tsx b/plugins/cost-insights/src/testUtils/providers.tsx index 46eab60163..29123d974b 100644 --- a/plugins/cost-insights/src/testUtils/providers.tsx +++ b/plugins/cost-insights/src/testUtils/providers.tsx @@ -95,6 +95,7 @@ export const MockConfigProvider = (props: MockConfigProviderProps) => { products: [], icons: [], engineerCost: 0, + engineerThreshold: 0.5, currencies: [], }; diff --git a/plugins/cost-insights/src/utils/change.test.ts b/plugins/cost-insights/src/utils/change.test.ts index b17e15797e..1a0b9947f5 100644 --- a/plugins/cost-insights/src/utils/change.test.ts +++ b/plugins/cost-insights/src/utils/change.test.ts @@ -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); }); }, ); diff --git a/plugins/cost-insights/src/utils/change.ts b/plugins/cost-insights/src/utils/change.ts index 3a0d19b123..43d58a021b 100644 --- a/plugins/cost-insights/src/utils/change.ts +++ b/plugins/cost-insights/src/utils/change.ts @@ -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;