@backstage-community/plugin-cost-insights 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/alerts/ProjectGrowthAlert.esm.js +2 -2
- package/dist/alerts/ProjectGrowthAlert.esm.js.map +1 -1
- package/dist/alerts/UnlabeledDataflowAlert.esm.js +2 -2
- package/dist/alerts/UnlabeledDataflowAlert.esm.js.map +1 -1
- package/dist/components/ActionItems/ActionItemCard.esm.js +3 -3
- package/dist/components/ActionItems/ActionItemCard.esm.js.map +1 -1
- package/dist/components/ActionItems/ActionItems.esm.js +52 -41
- package/dist/components/ActionItems/ActionItems.esm.js.map +1 -1
- package/dist/components/AlertInsights/AlertDialog.esm.js +79 -58
- package/dist/components/AlertInsights/AlertDialog.esm.js.map +1 -1
- package/dist/components/AlertInsights/AlertInsights.esm.js +57 -44
- package/dist/components/AlertInsights/AlertInsights.esm.js.map +1 -1
- package/dist/components/AlertInsights/AlertInsightsHeader.esm.js +10 -2
- package/dist/components/AlertInsights/AlertInsightsHeader.esm.js.map +1 -1
- package/dist/components/AlertInsights/AlertInsightsSection.esm.js +42 -34
- package/dist/components/AlertInsights/AlertInsightsSection.esm.js.map +1 -1
- package/dist/components/AlertInsights/AlertInsightsSectionHeader.esm.js +23 -12
- package/dist/components/AlertInsights/AlertInsightsSectionHeader.esm.js.map +1 -1
- package/dist/components/AlertInsights/AlertStatusSummary.esm.js +63 -55
- package/dist/components/AlertInsights/AlertStatusSummary.esm.js.map +1 -1
- package/dist/components/AlertInsights/AlertStatusSummaryButton.esm.js +7 -6
- package/dist/components/AlertInsights/AlertStatusSummaryButton.esm.js.map +1 -1
- package/dist/components/AlertInstructionsLayout/AlertInstructionsLayout.esm.js +23 -17
- package/dist/components/AlertInstructionsLayout/AlertInstructionsLayout.esm.js.map +1 -1
- package/dist/components/BarChart/BarChart.esm.js +83 -78
- package/dist/components/BarChart/BarChart.esm.js.map +1 -1
- package/dist/components/BarChart/BarChartLabel.esm.js +8 -5
- package/dist/components/BarChart/BarChartLabel.esm.js.map +1 -1
- package/dist/components/BarChart/BarChartLegend.esm.js +20 -16
- package/dist/components/BarChart/BarChartLegend.esm.js.map +1 -1
- package/dist/components/BarChart/BarChartStepper.esm.js +51 -48
- package/dist/components/BarChart/BarChartStepper.esm.js.map +1 -1
- package/dist/components/BarChart/BarChartStepperButton.esm.js +6 -5
- package/dist/components/BarChart/BarChartStepperButton.esm.js.map +1 -1
- package/dist/components/BarChart/BarChartSteps.esm.js +3 -3
- package/dist/components/BarChart/BarChartSteps.esm.js.map +1 -1
- package/dist/components/BarChart/BarChartTick.esm.js +5 -5
- package/dist/components/BarChart/BarChartTick.esm.js.map +1 -1
- package/dist/components/BarChart/BarChartTooltip.esm.js +27 -14
- package/dist/components/BarChart/BarChartTooltip.esm.js.map +1 -1
- package/dist/components/BarChart/BarChartTooltipItem.esm.js +11 -6
- package/dist/components/BarChart/BarChartTooltipItem.esm.js.map +1 -1
- package/dist/components/CopyUrlToClipboard/CopyUrlToClipboard.esm.js +3 -2
- package/dist/components/CopyUrlToClipboard/CopyUrlToClipboard.esm.js.map +1 -1
- package/dist/components/CostGrowth/CostGrowth.esm.js +26 -7
- package/dist/components/CostGrowth/CostGrowth.esm.js.map +1 -1
- package/dist/components/CostGrowth/CostGrowthIndicator.esm.js +6 -2
- package/dist/components/CostGrowth/CostGrowthIndicator.esm.js.map +1 -1
- package/dist/components/CostInsightsHeader/CostInsightsHeader.esm.js +77 -8
- package/dist/components/CostInsightsHeader/CostInsightsHeader.esm.js.map +1 -1
- package/dist/components/CostInsightsLayout/CostInsightsLayout.esm.js +16 -10
- package/dist/components/CostInsightsLayout/CostInsightsLayout.esm.js.map +1 -1
- package/dist/components/CostInsightsNavigation/CostInsightsNavigation.esm.js +41 -35
- package/dist/components/CostInsightsNavigation/CostInsightsNavigation.esm.js.map +1 -1
- package/dist/components/CostInsightsPage/CostInsightsPage.esm.js +116 -79
- package/dist/components/CostInsightsPage/CostInsightsPage.esm.js.map +1 -1
- package/dist/components/CostInsightsPage/CostInsightsPageRoot.esm.js +2 -2
- package/dist/components/CostInsightsPage/CostInsightsPageRoot.esm.js.map +1 -1
- package/dist/components/CostInsightsPage/CostInsightsThemeProvider.esm.js +5 -5
- package/dist/components/CostInsightsPage/CostInsightsThemeProvider.esm.js.map +1 -1
- package/dist/components/CostInsightsTabs/CostInsightsTabs.esm.js +58 -51
- package/dist/components/CostInsightsTabs/CostInsightsTabs.esm.js.map +1 -1
- package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js +74 -59
- package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js.map +1 -1
- package/dist/components/CostOverviewCard/CostOverviewCard.esm.js +45 -35
- package/dist/components/CostOverviewCard/CostOverviewCard.esm.js.map +1 -1
- package/dist/components/CostOverviewCard/CostOverviewChart.esm.js +86 -73
- package/dist/components/CostOverviewCard/CostOverviewChart.esm.js.map +1 -1
- package/dist/components/CostOverviewCard/CostOverviewHeader.esm.js +11 -6
- package/dist/components/CostOverviewCard/CostOverviewHeader.esm.js.map +1 -1
- package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js +21 -15
- package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js.map +1 -1
- package/dist/components/CurrencySelect/CurrencySelect.esm.js +24 -21
- package/dist/components/CurrencySelect/CurrencySelect.esm.js.map +1 -1
- package/dist/components/EntityCosts/EntityCosts.esm.js +8 -7
- package/dist/components/EntityCosts/EntityCosts.esm.js.map +1 -1
- package/dist/components/LabelDataflowInstructionsPage/LabelDataflowInstructionsPage.esm.js +65 -20
- package/dist/components/LabelDataflowInstructionsPage/LabelDataflowInstructionsPage.esm.js.map +1 -1
- package/dist/components/LegendItem/LegendItem.esm.js +34 -29
- package/dist/components/LegendItem/LegendItem.esm.js.map +1 -1
- package/dist/components/MetricSelect/MetricSelect.esm.js +19 -14
- package/dist/components/MetricSelect/MetricSelect.esm.js.map +1 -1
- package/dist/components/PeriodSelect/PeriodSelect.esm.js +15 -15
- package/dist/components/PeriodSelect/PeriodSelect.esm.js.map +1 -1
- package/dist/components/ProductInsights/ProductInsights.esm.js +12 -8
- package/dist/components/ProductInsights/ProductInsights.esm.js.map +1 -1
- package/dist/components/ProductInsightsCard/ProductEntityDialog.esm.js +20 -15
- package/dist/components/ProductInsightsCard/ProductEntityDialog.esm.js.map +1 -1
- package/dist/components/ProductInsightsCard/ProductEntityTable.esm.js +9 -9
- package/dist/components/ProductInsightsCard/ProductEntityTable.esm.js.map +1 -1
- package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js +25 -15
- package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js.map +1 -1
- package/dist/components/ProductInsightsCard/ProductInsightsCardList.esm.js +14 -14
- package/dist/components/ProductInsightsCard/ProductInsightsCardList.esm.js.map +1 -1
- package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js +43 -35
- package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js.map +1 -1
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.esm.js +5 -5
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.esm.js.map +1 -1
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.esm.js +5 -2
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.esm.js.map +1 -1
- package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js +55 -9
- package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js.map +1 -1
- package/dist/components/ProjectSelect/ProjectSelect.esm.js +15 -15
- package/dist/components/ProjectSelect/ProjectSelect.esm.js.map +1 -1
- package/dist/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.esm.js +12 -9
- package/dist/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.esm.js.map +1 -1
- package/dist/components/WhyCostsMatter/WhyCostsMatter.esm.js +40 -10
- package/dist/components/WhyCostsMatter/WhyCostsMatter.esm.js.map +1 -1
- package/dist/example/alerts/KubernetesMigrationAlert.esm.js +10 -3
- package/dist/example/alerts/KubernetesMigrationAlert.esm.js.map +1 -1
- package/dist/example/client.esm.js +1 -1
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.esm.js +20 -17
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.esm.js.map +1 -1
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js +3 -2
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js.map +1 -1
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js +14 -9
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js.map +1 -1
- package/dist/example/forms/KubernetesMigrationDismissForm.esm.js +21 -17
- package/dist/example/forms/KubernetesMigrationDismissForm.esm.js.map +1 -1
- package/dist/forms/AlertAcceptForm.esm.js +5 -4
- package/dist/forms/AlertAcceptForm.esm.js.map +1 -1
- package/dist/forms/AlertDismissForm.esm.js +47 -38
- package/dist/forms/AlertDismissForm.esm.js.map +1 -1
- package/dist/forms/AlertSnoozeForm.esm.js +20 -16
- package/dist/forms/AlertSnoozeForm.esm.js.map +1 -1
- package/dist/hooks/useConfig.esm.js +3 -2
- package/dist/hooks/useConfig.esm.js.map +1 -1
- package/dist/hooks/useCurrency.esm.js +6 -3
- package/dist/hooks/useCurrency.esm.js.map +1 -1
- package/dist/hooks/useFilters.esm.js +10 -7
- package/dist/hooks/useFilters.esm.js.map +1 -1
- package/dist/hooks/useGroups.esm.js +7 -4
- package/dist/hooks/useGroups.esm.js.map +1 -1
- package/dist/hooks/useLastCompleteBillingDate.esm.js +8 -7
- package/dist/hooks/useLastCompleteBillingDate.esm.js.map +1 -1
- package/dist/hooks/useLoading.esm.js +6 -2
- package/dist/hooks/useLoading.esm.js.map +1 -1
- package/dist/hooks/useScroll.esm.js +6 -3
- package/dist/hooks/useScroll.esm.js.map +1 -1
- package/dist/index.d.ts +17 -17
- package/dist/index.esm.js +2 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/testUtils/providers.esm.js +3 -3
- package/dist/testUtils/providers.esm.js.map +1 -1
- package/dist/utils/navigation.esm.js +10 -10
- package/dist/utils/navigation.esm.js.map +1 -1
- package/dist/utils/scroll.esm.js +3 -2
- package/dist/utils/scroll.esm.js.map +1 -1
- package/package.json +18 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductEntityTable.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductEntityTable.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport
|
|
1
|
+
{"version":3,"file":"ProductEntityTable.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductEntityTable.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport classnames from 'classnames';\nimport Typography from '@material-ui/core/Typography';\nimport { formatChange } from '../../utils/formatters';\nimport { useEntityDialogStyles as useStyles } from '../../utils/styles';\nimport { CostGrowthIndicator } from '../CostGrowth';\nimport { BarChartOptions } from '../../types';\nimport {\n ChangeStatistic,\n Entity,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { Table, TableColumn } from '@backstage/core-components';\nimport { useConfig } from '../../hooks';\n\nexport type ProductEntityTableOptions = Partial<\n Pick<BarChartOptions, 'previousName' | 'currentName'>\n>;\n\ntype RowData = {\n id: string;\n label: string;\n previous: number;\n current: number;\n change: ChangeStatistic;\n};\n\nconst createRenderer =\n (baseCurrency: Intl.NumberFormat) =>\n (col: keyof RowData, classes: Record<string, string>) => {\n return function render(rowData: {}): JSX.Element {\n const row = rowData as RowData;\n const rowStyles = classnames(classes.row, {\n [classes.rowTotal]: row.id === 'total',\n [classes.colFirst]: col === 'label',\n [classes.colLast]: col === 'change',\n });\n\n switch (col) {\n case 'previous':\n case 'current':\n return (\n <Typography className={rowStyles}>\n {baseCurrency.format(row[col])}\n </Typography>\n );\n case 'change':\n return (\n <CostGrowthIndicator\n className={rowStyles}\n change={row.change}\n formatter={formatChange}\n />\n );\n default:\n return <Typography className={rowStyles}>{row.label}</Typography>;\n }\n };\n };\n\n// material-table does not support fixed rows. Override the sorting algorithm\n// to force Total row to bottom by default or when a user sort toggles a column.\nfunction createSorter(field?: keyof Omit<RowData, 'id'>) {\n return function rowSort(data1: {}, data2: {}): number {\n const a = data1 as RowData;\n const b = data2 as RowData;\n if (a.id === 'total') return 1;\n if (b.id === 'total') return 1;\n if (field === 'label') return a.label.localeCompare(b.label);\n if (field === 'change') {\n if (formatChange(a[field]) === '∞' || formatChange(b[field]) === '-∞')\n return 1;\n if (formatChange(a[field]) === '-∞' || formatChange(b[field]) === '∞')\n return -1;\n return a[field].ratio! - b[field].ratio!;\n }\n\n return b.previous + b.current - (a.previous + a.current);\n };\n}\n\ntype ProductEntityTableProps = {\n entityLabel: string;\n entity: Entity;\n options: ProductEntityTableOptions;\n};\n\nexport const ProductEntityTable = ({\n entityLabel,\n entity,\n options,\n}: ProductEntityTableProps) => {\n const classes = useStyles();\n const { baseCurrency } = useConfig();\n const entities = entity.entities[entityLabel];\n\n const data = Object.assign(\n {\n previousName: 'Previous',\n currentName: 'Current',\n },\n options,\n );\n\n const firstColClasses = classnames(classes.column, classes.colFirst);\n const lastColClasses = classnames(classes.column, classes.colLast);\n\n const columns: TableColumn[] = [\n {\n field: 'label',\n title: <Typography className={firstColClasses}>{entityLabel}</Typography>,\n render: createRenderer(baseCurrency)('label', classes),\n customSort: createSorter('label'),\n width: '33.33%',\n },\n {\n field: 'previous',\n title: (\n <Typography className={classes.column}>{data.previousName}</Typography>\n ),\n align: 'right',\n render: createRenderer(baseCurrency)('previous', classes),\n customSort: createSorter('previous'),\n },\n {\n field: 'current',\n title: (\n <Typography className={classes.column}>{data.currentName}</Typography>\n ),\n align: 'right',\n render: createRenderer(baseCurrency)('current', classes),\n customSort: createSorter('current'),\n },\n {\n field: 'change',\n title: <Typography className={lastColClasses}>Change</Typography>,\n align: 'right',\n render: createRenderer(baseCurrency)('change', classes),\n customSort: createSorter('change'),\n },\n ];\n\n const rowData: RowData[] = entities\n .map(e => ({\n id: e.id || 'Unknown',\n label: e.id || 'Unknown',\n previous: e.aggregation[0],\n current: e.aggregation[1],\n change: e.change,\n }))\n .concat({\n id: 'total',\n label: 'Total',\n previous: entity.aggregation[0],\n current: entity.aggregation[1],\n change: entity.change,\n })\n .sort(createSorter());\n\n return (\n <Table\n columns={columns}\n data={rowData}\n title={entity.id || 'Unlabeled'}\n options={{\n paging: false,\n search: false,\n hideFilterIcons: true,\n }}\n />\n );\n};\n"],"names":["useStyles"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useRef, useState, useCallback, useEffect } from 'react';
|
|
2
3
|
import pluralize from 'pluralize';
|
|
3
4
|
import Typography from '@material-ui/core/Typography';
|
|
4
5
|
import Alert from '@material-ui/lab/Alert';
|
|
@@ -55,27 +56,36 @@ const ProductInsightsCard = ({
|
|
|
55
56
|
const subheader = entityKey && entities.length ? `${pluralize(entityKey, entities.length, true)}, sorted by cost` : null;
|
|
56
57
|
const headerProps = {
|
|
57
58
|
classes,
|
|
58
|
-
action: /* @__PURE__ */
|
|
59
|
+
action: /* @__PURE__ */ jsx(PeriodSelect, { duration, onSelect: setDuration })
|
|
59
60
|
};
|
|
60
61
|
if (error || !entity) {
|
|
61
|
-
return /* @__PURE__ */
|
|
62
|
+
return /* @__PURE__ */ jsxs(InfoCard, { title: product.name, headerProps, children: [
|
|
63
|
+
/* @__PURE__ */ jsx(ScrollAnchor, { id: product.kind }),
|
|
64
|
+
/* @__PURE__ */ jsx(Alert, { severity: "error", children: error ? error.message : `Error: Could not fetch product insights for ${product.name}` })
|
|
65
|
+
] });
|
|
62
66
|
}
|
|
63
|
-
return /* @__PURE__ */
|
|
67
|
+
return /* @__PURE__ */ jsxs(
|
|
64
68
|
InfoCard,
|
|
65
69
|
{
|
|
66
70
|
title: product.name,
|
|
67
71
|
subheader,
|
|
68
|
-
headerProps
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
headerProps,
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ jsx(ScrollAnchor, { id: product.kind }),
|
|
75
|
+
entities.length ? /* @__PURE__ */ jsx(
|
|
76
|
+
ProductInsightsChart,
|
|
77
|
+
{
|
|
78
|
+
entity,
|
|
79
|
+
duration,
|
|
80
|
+
billingDate: lastCompleteBillingDate
|
|
81
|
+
}
|
|
82
|
+
) : /* @__PURE__ */ jsxs(Typography, { children: [
|
|
83
|
+
"There are no ",
|
|
84
|
+
product.name,
|
|
85
|
+
" costs within this time frame for your team's projects."
|
|
86
|
+
] })
|
|
87
|
+
]
|
|
88
|
+
}
|
|
79
89
|
);
|
|
80
90
|
};
|
|
81
91
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductInsightsCard.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductInsightsCard.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport
|
|
1
|
+
{"version":3,"file":"ProductInsightsCard.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductInsightsCard.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PropsWithChildren,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport pluralize from 'pluralize';\nimport Typography from '@material-ui/core/Typography';\nimport { default as Alert } from '@material-ui/lab/Alert';\nimport { PeriodSelect } from '../PeriodSelect';\nimport { ProductInsightsChart } from './ProductInsightsChart';\nimport { useProductInsightsCardStyles as useStyles } from '../../utils/styles';\nimport { DefaultLoadingAction } from '../../utils/loading';\nimport { Duration } from '../../types';\nimport {\n Entity,\n Maybe,\n Product,\n} from '@backstage-community/plugin-cost-insights-common';\nimport {\n MapLoadingToProps,\n useLastCompleteBillingDate,\n useLoading,\n} from '../../hooks';\nimport { findAnyKey } from '../../utils/assert';\nimport { ScrollAnchor } from '../../utils/scroll';\nimport { InfoCard } from '@backstage/core-components';\n\ntype LoadingProps = (isLoading: boolean) => void;\n\nexport type ProductInsightsCardProps = {\n product: Product;\n initialState: {\n entity: Maybe<Entity>;\n duration: Duration;\n };\n onSelectAsync: (product: Product, duration: Duration) => Promise<Entity>;\n};\n\nconst mapLoadingToProps: MapLoadingToProps<LoadingProps> =\n ({ dispatch }) =>\n (isLoading: boolean) =>\n dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading });\n\nexport const ProductInsightsCard = ({\n initialState,\n product,\n onSelectAsync,\n}: PropsWithChildren<ProductInsightsCardProps>) => {\n const classes = useStyles();\n const mountedRef = useRef(false);\n const [error, setError] = useState<Maybe<Error>>(null);\n const dispatchLoading = useLoading(mapLoadingToProps);\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const [entity, setEntity] = useState<Maybe<Entity>>(initialState.entity);\n const [duration, setDuration] = useState<Duration>(initialState.duration);\n\n /* eslint-disable react-hooks/exhaustive-deps */\n const dispatchLoadingProduct = useCallback(dispatchLoading, []);\n /* eslint-enable react-hooks/exhaustive-deps */\n\n useEffect(() => {\n async function handleOnSelectAsync() {\n dispatchLoadingProduct(true);\n try {\n const e = await onSelectAsync(product, duration);\n setEntity(e);\n } catch (e) {\n setEntity(null);\n setError(e);\n } finally {\n dispatchLoadingProduct(false);\n }\n }\n\n if (mountedRef.current) {\n handleOnSelectAsync();\n } else {\n mountedRef.current = true;\n }\n }, [product, duration, onSelectAsync, dispatchLoadingProduct]);\n\n // Only a single entities Record for the root product entity is supported\n const entityKey = findAnyKey(entity?.entities);\n const entities = entityKey ? entity!.entities[entityKey] : [];\n\n const subheader =\n entityKey && entities.length\n ? `${pluralize(entityKey, entities.length, true)}, sorted by cost`\n : null;\n const headerProps = {\n classes: classes,\n action: <PeriodSelect duration={duration} onSelect={setDuration} />,\n };\n\n if (error || !entity) {\n return (\n <InfoCard title={product.name} headerProps={headerProps}>\n <ScrollAnchor id={product.kind} />\n <Alert severity=\"error\">\n {error\n ? error.message\n : `Error: Could not fetch product insights for ${product.name}`}\n </Alert>\n </InfoCard>\n );\n }\n\n return (\n <InfoCard\n title={product.name}\n subheader={subheader}\n headerProps={headerProps}\n >\n <ScrollAnchor id={product.kind} />\n {entities.length ? (\n <ProductInsightsChart\n entity={entity}\n duration={duration}\n billingDate={lastCompleteBillingDate}\n />\n ) : (\n <Typography>\n There are no {product.name} costs within this time frame for your\n team's projects.\n </Typography>\n )}\n </InfoCard>\n );\n};\n"],"names":["useStyles"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwDA,MAAM,iBACJ,GAAA,CAAC,EAAE,QAAA,OACH,CAAC,SAAA,KACC,QAAS,CAAA,EAAE,CAAC,oBAAA,CAAqB,oBAAoB,GAAG,WAAW,CAAA;AAEhE,MAAM,sBAAsB,CAAC;AAAA,EAClC,YAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAmD,KAAA;AACjD,EAAA,MAAM,UAAUA,4BAAU,EAAA;AAC1B,EAAM,MAAA,UAAA,GAAa,OAAO,KAAK,CAAA;AAC/B,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AACrD,EAAM,MAAA,eAAA,GAAkB,WAAW,iBAAiB,CAAA;AACpD,EAAA,MAAM,0BAA0B,0BAA2B,EAAA;AAC3D,EAAA,MAAM,CAAC,MAAQ,EAAA,SAAS,CAAI,GAAA,QAAA,CAAwB,aAAa,MAAM,CAAA;AACvE,EAAA,MAAM,CAAC,QAAU,EAAA,WAAW,CAAI,GAAA,QAAA,CAAmB,aAAa,QAAQ,CAAA;AAGxE,EAAA,MAAM,sBAAyB,GAAA,WAAA,CAAY,eAAiB,EAAA,EAAE,CAAA;AAG9D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,eAAe,mBAAsB,GAAA;AACnC,MAAA,sBAAA,CAAuB,IAAI,CAAA;AAC3B,MAAI,IAAA;AACF,QAAA,MAAM,CAAI,GAAA,MAAM,aAAc,CAAA,OAAA,EAAS,QAAQ,CAAA;AAC/C,QAAA,SAAA,CAAU,CAAC,CAAA;AAAA,eACJ,CAAG,EAAA;AACV,QAAA,SAAA,CAAU,IAAI,CAAA;AACd,QAAA,QAAA,CAAS,CAAC,CAAA;AAAA,OACV,SAAA;AACA,QAAA,sBAAA,CAAuB,KAAK,CAAA;AAAA;AAC9B;AAGF,IAAA,IAAI,WAAW,OAAS,EAAA;AACtB,MAAoB,mBAAA,EAAA;AAAA,KACf,MAAA;AACL,MAAA,UAAA,CAAW,OAAU,GAAA,IAAA;AAAA;AACvB,KACC,CAAC,OAAA,EAAS,QAAU,EAAA,aAAA,EAAe,sBAAsB,CAAC,CAAA;AAG7D,EAAM,MAAA,SAAA,GAAY,UAAW,CAAA,MAAA,EAAQ,QAAQ,CAAA;AAC7C,EAAA,MAAM,WAAW,SAAY,GAAA,MAAA,CAAQ,QAAS,CAAA,SAAS,IAAI,EAAC;AAE5D,EAAM,MAAA,SAAA,GACJ,SAAa,IAAA,QAAA,CAAS,MAClB,GAAA,CAAA,EAAG,SAAU,CAAA,SAAA,EAAW,QAAS,CAAA,MAAA,EAAQ,IAAI,CAAC,CAC9C,gBAAA,CAAA,GAAA,IAAA;AACN,EAAA,MAAM,WAAc,GAAA;AAAA,IAClB,OAAA;AAAA,IACA,MAAQ,kBAAA,GAAA,CAAC,YAAa,EAAA,EAAA,QAAA,EAAoB,UAAU,WAAa,EAAA;AAAA,GACnE;AAEA,EAAI,IAAA,KAAA,IAAS,CAAC,MAAQ,EAAA;AACpB,IAAA,uBACG,IAAA,CAAA,QAAA,EAAA,EAAS,KAAO,EAAA,OAAA,CAAQ,MAAM,WAC7B,EAAA,QAAA,EAAA;AAAA,sBAAC,GAAA,CAAA,YAAA,EAAA,EAAa,EAAI,EAAA,OAAA,CAAQ,IAAM,EAAA,CAAA;AAAA,sBAChC,GAAA,CAAC,KAAM,EAAA,EAAA,QAAA,EAAS,OACb,EAAA,QAAA,EAAA,KAAA,GACG,MAAM,OACN,GAAA,CAAA,4CAAA,EAA+C,OAAQ,CAAA,IAAI,CACjE,CAAA,EAAA;AAAA,KACF,EAAA,CAAA;AAAA;AAIJ,EACE,uBAAA,IAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,OAAO,OAAQ,CAAA,IAAA;AAAA,MACf,SAAA;AAAA,MACA,WAAA;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAC,GAAA,CAAA,YAAA,EAAA,EAAa,EAAI,EAAA,OAAA,CAAQ,IAAM,EAAA,CAAA;AAAA,QAC/B,SAAS,MACR,mBAAA,GAAA;AAAA,UAAC,oBAAA;AAAA,UAAA;AAAA,YACC,MAAA;AAAA,YACA,QAAA;AAAA,YACA,WAAa,EAAA;AAAA;AAAA,SACf,wBAEC,UAAW,EAAA,EAAA,QAAA,EAAA;AAAA,UAAA,eAAA;AAAA,UACI,OAAQ,CAAA,IAAA;AAAA,UAAK;AAAA,SAE7B,EAAA;AAAA;AAAA;AAAA,GAEJ;AAEJ;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import Box from '@material-ui/core/Box';
|
|
3
3
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
|
4
4
|
import Collapse from '@material-ui/core/Collapse';
|
|
@@ -9,25 +9,25 @@ const ProductInsightsCardList = ({
|
|
|
9
9
|
onSelectAsync
|
|
10
10
|
}) => {
|
|
11
11
|
if (!initialStates.length) {
|
|
12
|
-
return /* @__PURE__ */
|
|
12
|
+
return /* @__PURE__ */ jsx(Box, { display: "flex", justifyContent: "space-around", alignItems: "center", children: /* @__PURE__ */ jsx(CircularProgress, {}) });
|
|
13
13
|
}
|
|
14
|
-
return /* @__PURE__ */
|
|
14
|
+
return /* @__PURE__ */ jsx(Collapse, { in: true, timeout: 1e3, children: initialStates.map(({ product, entity, duration }) => /* @__PURE__ */ jsx(
|
|
15
15
|
Box,
|
|
16
16
|
{
|
|
17
|
-
key: product.kind,
|
|
18
17
|
mb: 6,
|
|
19
18
|
position: "relative",
|
|
20
|
-
"data-testid": `product-list-item-${product.kind}
|
|
19
|
+
"data-testid": `product-list-item-${product.kind}`,
|
|
20
|
+
children: /* @__PURE__ */ jsx(
|
|
21
|
+
ProductInsightsCard,
|
|
22
|
+
{
|
|
23
|
+
product,
|
|
24
|
+
onSelectAsync,
|
|
25
|
+
initialState: { entity, duration }
|
|
26
|
+
}
|
|
27
|
+
)
|
|
21
28
|
},
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
product,
|
|
26
|
-
onSelectAsync,
|
|
27
|
-
initialState: { entity, duration }
|
|
28
|
-
}
|
|
29
|
-
)
|
|
30
|
-
)));
|
|
29
|
+
product.kind
|
|
30
|
+
)) });
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export { ProductInsightsCardList };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductInsightsCardList.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductInsightsCardList.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport
|
|
1
|
+
{"version":3,"file":"ProductInsightsCardList.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductInsightsCardList.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Box from '@material-ui/core/Box';\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport Collapse from '@material-ui/core/Collapse';\nimport { ProductInsightsCard } from './ProductInsightsCard';\nimport { Duration } from '../../types';\nimport {\n Entity,\n Product,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { ProductState } from '../../utils/loading';\n\ntype ProductInsightsCardListProps = {\n initialStates: ProductState[];\n onSelectAsync: (product: Product, duration: Duration) => Promise<Entity>;\n};\n\nexport const ProductInsightsCardList = ({\n initialStates,\n onSelectAsync,\n}: ProductInsightsCardListProps) => {\n if (!initialStates.length) {\n return (\n <Box display=\"flex\" justifyContent=\"space-around\" alignItems=\"center\">\n <CircularProgress />\n </Box>\n );\n }\n\n return (\n <Collapse in timeout={1000}>\n {initialStates.map(({ product, entity, duration }) => (\n <Box\n key={product.kind}\n mb={6}\n position=\"relative\"\n data-testid={`product-list-item-${product.kind}`}\n >\n <ProductInsightsCard\n product={product}\n onSelectAsync={onSelectAsync}\n initialState={{ entity: entity, duration: duration }}\n />\n </Box>\n ))}\n </Collapse>\n );\n};\n"],"names":[],"mappings":";;;;;;AAgCO,MAAM,0BAA0B,CAAC;AAAA,EACtC,aAAA;AAAA,EACA;AACF,CAAoC,KAAA;AAClC,EAAI,IAAA,CAAC,cAAc,MAAQ,EAAA;AACzB,IACE,uBAAA,GAAA,CAAC,GAAI,EAAA,EAAA,OAAA,EAAQ,MAAO,EAAA,cAAA,EAAe,gBAAe,UAAW,EAAA,QAAA,EAC3D,QAAC,kBAAA,GAAA,CAAA,gBAAA,EAAA,EAAiB,CACpB,EAAA,CAAA;AAAA;AAIJ,EAAA,uBACG,GAAA,CAAA,QAAA,EAAA,EAAS,EAAE,EAAA,IAAA,EAAC,OAAS,EAAA,GAAA,EACnB,QAAc,EAAA,aAAA,CAAA,GAAA,CAAI,CAAC,EAAE,OAAS,EAAA,MAAA,EAAQ,UACrC,qBAAA,GAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MAEC,EAAI,EAAA,CAAA;AAAA,MACJ,QAAS,EAAA,UAAA;AAAA,MACT,aAAA,EAAa,CAAqB,kBAAA,EAAA,OAAA,CAAQ,IAAI,CAAA,CAAA;AAAA,MAE9C,QAAA,kBAAA,GAAA;AAAA,QAAC,mBAAA;AAAA,QAAA;AAAA,UACC,OAAA;AAAA,UACA,aAAA;AAAA,UACA,YAAA,EAAc,EAAE,MAAA,EAAgB,QAAmB;AAAA;AAAA;AACrD,KAAA;AAAA,IATK,OAAQ,CAAA;AAAA,GAWhB,CACH,EAAA,CAAA;AAEJ;;;;"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
2
3
|
import pluralize from 'pluralize';
|
|
3
4
|
import Box from '@material-ui/core/Box';
|
|
4
5
|
import Typography from '@material-ui/core/Typography';
|
|
@@ -82,12 +83,12 @@ const ProductInsightsChart = ({
|
|
|
82
83
|
const breakdowns = Object.keys(activeEntity.entities);
|
|
83
84
|
if (breakdowns.length) {
|
|
84
85
|
const subtitle = breakdowns.map((b) => pluralize(b, activeEntity.entities[b].length, true)).join(", ");
|
|
85
|
-
return /* @__PURE__ */
|
|
86
|
+
return /* @__PURE__ */ jsx(
|
|
86
87
|
BarChartTooltip,
|
|
87
88
|
{
|
|
88
89
|
title,
|
|
89
90
|
subtitle,
|
|
90
|
-
topRight: !!activeEntity.change.ratio && /* @__PURE__ */
|
|
91
|
+
topRight: !!activeEntity.change.ratio && /* @__PURE__ */ jsx(
|
|
91
92
|
CostGrowthIndicator,
|
|
92
93
|
{
|
|
93
94
|
formatter: formatChange,
|
|
@@ -95,16 +96,19 @@ const ProductInsightsChart = ({
|
|
|
95
96
|
className: classes.indicator
|
|
96
97
|
}
|
|
97
98
|
),
|
|
98
|
-
actions: /* @__PURE__ */
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
actions: /* @__PURE__ */ jsxs(Box, { className: classes.actions, children: [
|
|
100
|
+
/* @__PURE__ */ jsx(FullScreenIcon, {}),
|
|
101
|
+
/* @__PURE__ */ jsx(Typography, { children: "Click for breakdown" })
|
|
102
|
+
] }),
|
|
103
|
+
children: items.map((item, index) => /* @__PURE__ */ jsx(BarChartTooltipItem, { item }, `${item.label}-${index}`))
|
|
104
|
+
}
|
|
101
105
|
);
|
|
102
106
|
}
|
|
103
|
-
return /* @__PURE__ */
|
|
107
|
+
return /* @__PURE__ */ jsx(
|
|
104
108
|
BarChartTooltip,
|
|
105
109
|
{
|
|
106
110
|
title,
|
|
107
|
-
topRight: !!activeEntity.change.ratio && /* @__PURE__ */
|
|
111
|
+
topRight: !!activeEntity.change.ratio && /* @__PURE__ */ jsx(
|
|
108
112
|
CostGrowthIndicator,
|
|
109
113
|
{
|
|
110
114
|
formatter: formatChange,
|
|
@@ -112,36 +116,40 @@ const ProductInsightsChart = ({
|
|
|
112
116
|
className: classes.indicator
|
|
113
117
|
}
|
|
114
118
|
),
|
|
115
|
-
content: id ? null : "This product has costs that are not labeled and therefore can't be attributed to a specific entity."
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
content: id ? null : "This product has costs that are not labeled and therefore can't be attributed to a specific entity.",
|
|
120
|
+
children: items.map((item, index) => /* @__PURE__ */ jsx(BarChartTooltipItem, { item }, `${item.label}-${index}`))
|
|
121
|
+
}
|
|
118
122
|
);
|
|
119
123
|
};
|
|
120
124
|
const barChartProps = isClickable ? { onClick } : {};
|
|
121
|
-
return /* @__PURE__ */
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
125
|
+
return /* @__PURE__ */ jsxs(Box, { className: layoutClasses.wrapper, children: [
|
|
126
|
+
/* @__PURE__ */ jsx(BarChartLegend, { costStart, costEnd, options, children: /* @__PURE__ */ jsx(
|
|
127
|
+
LegendItem,
|
|
128
|
+
{
|
|
129
|
+
title: choose(["Cost Savings", "Cost Excess"], entity.change),
|
|
130
|
+
children: /* @__PURE__ */ jsx(CostGrowth, { change: entity.change, duration })
|
|
131
|
+
}
|
|
132
|
+
) }),
|
|
133
|
+
/* @__PURE__ */ jsx(
|
|
134
|
+
BarChart,
|
|
135
|
+
{
|
|
136
|
+
resources,
|
|
137
|
+
tooltip: renderProductInsightsTooltip,
|
|
138
|
+
onMouseMove,
|
|
139
|
+
options,
|
|
140
|
+
...barChartProps
|
|
141
|
+
}
|
|
142
|
+
),
|
|
143
|
+
isSelected && entities.length && /* @__PURE__ */ jsx(
|
|
144
|
+
ProductEntityDialog,
|
|
145
|
+
{
|
|
146
|
+
open: isSelected,
|
|
147
|
+
onClose: () => setSelected(void 0),
|
|
148
|
+
entity: findAlways(entities, (e) => e.id === selectLabel),
|
|
149
|
+
options
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
] });
|
|
145
153
|
};
|
|
146
154
|
|
|
147
155
|
export { ProductInsightsChart };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductInsightsChart.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductInsightsChart.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { useMemo, useState } from 'react';\nimport pluralize from 'pluralize';\nimport Box from '@material-ui/core/Box';\nimport Typography from '@material-ui/core/Typography';\nimport { default as FullScreenIcon } from '@material-ui/icons/Fullscreen';\nimport { LegendItem } from '../LegendItem';\nimport { ProductEntityDialog } from './ProductEntityDialog';\nimport { CostGrowth, CostGrowthIndicator } from '../CostGrowth';\nimport {\n BarChart,\n BarChartLegend,\n BarChartTooltip,\n BarChartTooltipItem,\n BarChartLegendOptions,\n} from '../BarChart';\nimport {\n findAlways,\n notEmpty,\n isUndefined,\n findAnyKey,\n assertAlways,\n} from '../../utils/assert';\nimport { formatPeriod, formatChange } from '../../utils/formatters';\nimport {\n titleOf,\n tooltipItemOf,\n resourceOf,\n isInvalid,\n isLabeled,\n isUnlabeled,\n} from '../../utils/graphs';\nimport {\n useProductInsightsChartStyles as useStyles,\n useBarChartLayoutStyles as useLayoutStyles,\n} from '../../utils/styles';\nimport { Duration } from '../../types';\nimport {\n Entity,\n Maybe,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { choose } from '../../utils/change';\nimport { TooltipRenderer } from '../../types';\nimport { useConfig } from '../../hooks';\n\nexport type ProductInsightsChartProps = {\n billingDate: string;\n entity: Entity;\n duration: Duration;\n};\n\nexport const ProductInsightsChart = ({\n billingDate,\n entity,\n duration,\n}: ProductInsightsChartProps) => {\n const classes = useStyles();\n const layoutClasses = useLayoutStyles();\n const { baseCurrency } = useConfig();\n\n // Only a single entities Record for the root product entity is supported\n const entities = useMemo(() => {\n const entityLabel = assertAlways(findAnyKey(entity.entities));\n return entity.entities[entityLabel] ?? [];\n }, [entity]);\n\n const [activeLabel, setActive] = useState<Maybe<string>>();\n const [selectLabel, setSelected] = useState<Maybe<string>>();\n const isSelected = useMemo(() => !isUndefined(selectLabel), [selectLabel]);\n\n const isClickable = useMemo(() => {\n const breakdowns = Object.keys(\n entities.find(e => e.id === activeLabel)?.entities ?? {},\n );\n return breakdowns.length > 0;\n }, [entities, activeLabel]);\n\n const costStart = entity.aggregation[0];\n const costEnd = entity.aggregation[1];\n const resources = entities.map(resourceOf);\n\n const options: Partial<BarChartLegendOptions> = {\n previousName: formatPeriod(duration, billingDate, false),\n currentName: formatPeriod(duration, billingDate, true),\n };\n\n const onMouseMove: (...args: any[]) => void = (\n data: Record<'activeLabel', string | undefined>,\n ) => {\n if (isLabeled(data)) {\n setActive(data.activeLabel!);\n } else if (isUnlabeled(data)) {\n setActive(null);\n } else {\n setActive(undefined);\n }\n };\n\n const onClick: (...args: any[]) => void = (\n data: Record<'activeLabel', string>,\n ) => {\n if (isLabeled(data)) {\n setSelected(data.activeLabel);\n } else if (isUnlabeled(data)) {\n setSelected(null);\n } else {\n setSelected(undefined);\n }\n };\n\n const renderProductInsightsTooltip: TooltipRenderer = ({\n label,\n payload = [],\n }) => {\n /* Labels and payloads may be undefined or empty */\n if (isInvalid({ label, payload })) return null;\n\n /*\n * recharts coerces null values to strings\n * entity -> resource -> payload\n * { id: null } -> { name: null } -> { label: '' }\n */\n const id = label === '' ? null : label;\n\n const title = titleOf(label);\n const items = payload.map(tooltipItemOf(baseCurrency)).filter(notEmpty);\n\n const activeEntity = findAlways(entities, e => e.id === id);\n const breakdowns = Object.keys(activeEntity.entities);\n\n if (breakdowns.length) {\n const subtitle = breakdowns\n .map(b => pluralize(b, activeEntity.entities[b].length, true))\n .join(', ');\n return (\n <BarChartTooltip\n title={title}\n subtitle={subtitle}\n topRight={\n !!activeEntity.change.ratio && (\n <CostGrowthIndicator\n formatter={formatChange}\n change={activeEntity.change}\n className={classes.indicator}\n />\n )\n }\n actions={\n <Box className={classes.actions}>\n <FullScreenIcon />\n <Typography>Click for breakdown</Typography>\n </Box>\n }\n >\n {items.map((item, index) => (\n <BarChartTooltipItem key={`${item.label}-${index}`} item={item} />\n ))}\n </BarChartTooltip>\n );\n }\n\n // If an entity doesn't have any sub-entities, there aren't any costs to break down.\n return (\n <BarChartTooltip\n title={title}\n topRight={\n !!activeEntity.change.ratio && (\n <CostGrowthIndicator\n formatter={formatChange}\n change={activeEntity.change}\n className={classes.indicator}\n />\n )\n }\n content={\n id\n ? null\n : \"This product has costs that are not labeled and therefore can't be attributed to a specific entity.\"\n }\n >\n {items.map((item, index) => (\n <BarChartTooltipItem key={`${item.label}-${index}`} item={item} />\n ))}\n </BarChartTooltip>\n );\n };\n\n const barChartProps = isClickable ? { onClick } : {};\n\n return (\n <Box className={layoutClasses.wrapper}>\n <BarChartLegend costStart={costStart} costEnd={costEnd} options={options}>\n <LegendItem\n title={choose(['Cost Savings', 'Cost Excess'], entity.change)}\n >\n <CostGrowth change={entity.change} duration={duration} />\n </LegendItem>\n </BarChartLegend>\n <BarChart\n resources={resources}\n tooltip={renderProductInsightsTooltip}\n onMouseMove={onMouseMove}\n options={options}\n {...barChartProps}\n />\n {isSelected && entities.length && (\n <ProductEntityDialog\n open={isSelected}\n onClose={() => setSelected(undefined)}\n entity={findAlways(entities, e => e.id === selectLabel)}\n options={options}\n />\n )}\n </Box>\n );\n};\n"],"names":["useStyles","useLayoutStyles"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProductInsightsChart.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductInsightsChart.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useMemo, useState } from 'react';\nimport pluralize from 'pluralize';\nimport Box from '@material-ui/core/Box';\nimport Typography from '@material-ui/core/Typography';\nimport { default as FullScreenIcon } from '@material-ui/icons/Fullscreen';\nimport { LegendItem } from '../LegendItem';\nimport { ProductEntityDialog } from './ProductEntityDialog';\nimport { CostGrowth, CostGrowthIndicator } from '../CostGrowth';\nimport {\n BarChart,\n BarChartLegend,\n BarChartTooltip,\n BarChartTooltipItem,\n BarChartLegendOptions,\n} from '../BarChart';\nimport {\n findAlways,\n notEmpty,\n isUndefined,\n findAnyKey,\n assertAlways,\n} from '../../utils/assert';\nimport { formatPeriod, formatChange } from '../../utils/formatters';\nimport {\n titleOf,\n tooltipItemOf,\n resourceOf,\n isInvalid,\n isLabeled,\n isUnlabeled,\n} from '../../utils/graphs';\nimport {\n useProductInsightsChartStyles as useStyles,\n useBarChartLayoutStyles as useLayoutStyles,\n} from '../../utils/styles';\nimport { Duration } from '../../types';\nimport {\n Entity,\n Maybe,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { choose } from '../../utils/change';\nimport { TooltipRenderer } from '../../types';\nimport { useConfig } from '../../hooks';\n\nexport type ProductInsightsChartProps = {\n billingDate: string;\n entity: Entity;\n duration: Duration;\n};\n\nexport const ProductInsightsChart = ({\n billingDate,\n entity,\n duration,\n}: ProductInsightsChartProps) => {\n const classes = useStyles();\n const layoutClasses = useLayoutStyles();\n const { baseCurrency } = useConfig();\n\n // Only a single entities Record for the root product entity is supported\n const entities = useMemo(() => {\n const entityLabel = assertAlways(findAnyKey(entity.entities));\n return entity.entities[entityLabel] ?? [];\n }, [entity]);\n\n const [activeLabel, setActive] = useState<Maybe<string>>();\n const [selectLabel, setSelected] = useState<Maybe<string>>();\n const isSelected = useMemo(() => !isUndefined(selectLabel), [selectLabel]);\n\n const isClickable = useMemo(() => {\n const breakdowns = Object.keys(\n entities.find(e => e.id === activeLabel)?.entities ?? {},\n );\n return breakdowns.length > 0;\n }, [entities, activeLabel]);\n\n const costStart = entity.aggregation[0];\n const costEnd = entity.aggregation[1];\n const resources = entities.map(resourceOf);\n\n const options: Partial<BarChartLegendOptions> = {\n previousName: formatPeriod(duration, billingDate, false),\n currentName: formatPeriod(duration, billingDate, true),\n };\n\n const onMouseMove: (...args: any[]) => void = (\n data: Record<'activeLabel', string | undefined>,\n ) => {\n if (isLabeled(data)) {\n setActive(data.activeLabel!);\n } else if (isUnlabeled(data)) {\n setActive(null);\n } else {\n setActive(undefined);\n }\n };\n\n const onClick: (...args: any[]) => void = (\n data: Record<'activeLabel', string>,\n ) => {\n if (isLabeled(data)) {\n setSelected(data.activeLabel);\n } else if (isUnlabeled(data)) {\n setSelected(null);\n } else {\n setSelected(undefined);\n }\n };\n\n const renderProductInsightsTooltip: TooltipRenderer = ({\n label,\n payload = [],\n }) => {\n /* Labels and payloads may be undefined or empty */\n if (isInvalid({ label, payload })) return null;\n\n /*\n * recharts coerces null values to strings\n * entity -> resource -> payload\n * { id: null } -> { name: null } -> { label: '' }\n */\n const id = label === '' ? null : label;\n\n const title = titleOf(label);\n const items = payload.map(tooltipItemOf(baseCurrency)).filter(notEmpty);\n\n const activeEntity = findAlways(entities, e => e.id === id);\n const breakdowns = Object.keys(activeEntity.entities);\n\n if (breakdowns.length) {\n const subtitle = breakdowns\n .map(b => pluralize(b, activeEntity.entities[b].length, true))\n .join(', ');\n return (\n <BarChartTooltip\n title={title}\n subtitle={subtitle}\n topRight={\n !!activeEntity.change.ratio && (\n <CostGrowthIndicator\n formatter={formatChange}\n change={activeEntity.change}\n className={classes.indicator}\n />\n )\n }\n actions={\n <Box className={classes.actions}>\n <FullScreenIcon />\n <Typography>Click for breakdown</Typography>\n </Box>\n }\n >\n {items.map((item, index) => (\n <BarChartTooltipItem key={`${item.label}-${index}`} item={item} />\n ))}\n </BarChartTooltip>\n );\n }\n\n // If an entity doesn't have any sub-entities, there aren't any costs to break down.\n return (\n <BarChartTooltip\n title={title}\n topRight={\n !!activeEntity.change.ratio && (\n <CostGrowthIndicator\n formatter={formatChange}\n change={activeEntity.change}\n className={classes.indicator}\n />\n )\n }\n content={\n id\n ? null\n : \"This product has costs that are not labeled and therefore can't be attributed to a specific entity.\"\n }\n >\n {items.map((item, index) => (\n <BarChartTooltipItem key={`${item.label}-${index}`} item={item} />\n ))}\n </BarChartTooltip>\n );\n };\n\n const barChartProps = isClickable ? { onClick } : {};\n\n return (\n <Box className={layoutClasses.wrapper}>\n <BarChartLegend costStart={costStart} costEnd={costEnd} options={options}>\n <LegendItem\n title={choose(['Cost Savings', 'Cost Excess'], entity.change)}\n >\n <CostGrowth change={entity.change} duration={duration} />\n </LegendItem>\n </BarChartLegend>\n <BarChart\n resources={resources}\n tooltip={renderProductInsightsTooltip}\n onMouseMove={onMouseMove}\n options={options}\n {...barChartProps}\n />\n {isSelected && entities.length && (\n <ProductEntityDialog\n open={isSelected}\n onClose={() => setSelected(undefined)}\n entity={findAlways(entities, e => e.id === selectLabel)}\n options={options}\n />\n )}\n </Box>\n );\n};\n"],"names":["useStyles","useLayoutStyles"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAkEO,MAAM,uBAAuB,CAAC;AAAA,EACnC,WAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAiC,KAAA;AAC/B,EAAA,MAAM,UAAUA,6BAAU,EAAA;AAC1B,EAAA,MAAM,gBAAgBC,uBAAgB,EAAA;AACtC,EAAM,MAAA,EAAE,YAAa,EAAA,GAAI,SAAU,EAAA;AAGnC,EAAM,MAAA,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,MAAM,WAAc,GAAA,YAAA,CAAa,UAAW,CAAA,MAAA,CAAO,QAAQ,CAAC,CAAA;AAC5D,IAAA,OAAO,MAAO,CAAA,QAAA,CAAS,WAAW,CAAA,IAAK,EAAC;AAAA,GAC1C,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,CAAC,WAAA,EAAa,SAAS,CAAA,GAAI,QAAwB,EAAA;AACzD,EAAA,MAAM,CAAC,WAAA,EAAa,WAAW,CAAA,GAAI,QAAwB,EAAA;AAC3D,EAAM,MAAA,UAAA,GAAa,QAAQ,MAAM,CAAC,YAAY,WAAW,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAEzE,EAAM,MAAA,WAAA,GAAc,QAAQ,MAAM;AAChC,IAAA,MAAM,aAAa,MAAO,CAAA,IAAA;AAAA,MACxB,QAAA,CAAS,KAAK,CAAK,CAAA,KAAA,CAAA,CAAE,OAAO,WAAW,CAAA,EAAG,YAAY;AAAC,KACzD;AACA,IAAA,OAAO,WAAW,MAAS,GAAA,CAAA;AAAA,GAC1B,EAAA,CAAC,QAAU,EAAA,WAAW,CAAC,CAAA;AAE1B,EAAM,MAAA,SAAA,GAAY,MAAO,CAAA,WAAA,CAAY,CAAC,CAAA;AACtC,EAAM,MAAA,OAAA,GAAU,MAAO,CAAA,WAAA,CAAY,CAAC,CAAA;AACpC,EAAM,MAAA,SAAA,GAAY,QAAS,CAAA,GAAA,CAAI,UAAU,CAAA;AAEzC,EAAA,MAAM,OAA0C,GAAA;AAAA,IAC9C,YAAc,EAAA,YAAA,CAAa,QAAU,EAAA,WAAA,EAAa,KAAK,CAAA;AAAA,IACvD,WAAa,EAAA,YAAA,CAAa,QAAU,EAAA,WAAA,EAAa,IAAI;AAAA,GACvD;AAEA,EAAM,MAAA,WAAA,GAAwC,CAC5C,IACG,KAAA;AACH,IAAI,IAAA,SAAA,CAAU,IAAI,CAAG,EAAA;AACnB,MAAA,SAAA,CAAU,KAAK,WAAY,CAAA;AAAA,KAC7B,MAAA,IAAW,WAAY,CAAA,IAAI,CAAG,EAAA;AAC5B,MAAA,SAAA,CAAU,IAAI,CAAA;AAAA,KACT,MAAA;AACL,MAAA,SAAA,CAAU,KAAS,CAAA,CAAA;AAAA;AACrB,GACF;AAEA,EAAM,MAAA,OAAA,GAAoC,CACxC,IACG,KAAA;AACH,IAAI,IAAA,SAAA,CAAU,IAAI,CAAG,EAAA;AACnB,MAAA,WAAA,CAAY,KAAK,WAAW,CAAA;AAAA,KAC9B,MAAA,IAAW,WAAY,CAAA,IAAI,CAAG,EAAA;AAC5B,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,KACX,MAAA;AACL,MAAA,WAAA,CAAY,KAAS,CAAA,CAAA;AAAA;AACvB,GACF;AAEA,EAAA,MAAM,+BAAgD,CAAC;AAAA,IACrD,KAAA;AAAA,IACA,UAAU;AAAC,GACP,KAAA;AAEJ,IAAA,IAAI,UAAU,EAAE,KAAA,EAAO,OAAQ,EAAC,GAAU,OAAA,IAAA;AAO1C,IAAM,MAAA,EAAA,GAAK,KAAU,KAAA,EAAA,GAAK,IAAO,GAAA,KAAA;AAEjC,IAAM,MAAA,KAAA,GAAQ,QAAQ,KAAK,CAAA;AAC3B,IAAM,MAAA,KAAA,GAAQ,QAAQ,GAAI,CAAA,aAAA,CAAc,YAAY,CAAC,CAAA,CAAE,OAAO,QAAQ,CAAA;AAEtE,IAAA,MAAM,eAAe,UAAW,CAAA,QAAA,EAAU,CAAK,CAAA,KAAA,CAAA,CAAE,OAAO,EAAE,CAAA;AAC1D,IAAA,MAAM,UAAa,GAAA,MAAA,CAAO,IAAK,CAAA,YAAA,CAAa,QAAQ,CAAA;AAEpD,IAAA,IAAI,WAAW,MAAQ,EAAA;AACrB,MAAA,MAAM,QAAW,GAAA,UAAA,CACd,GAAI,CAAA,CAAA,CAAA,KAAK,UAAU,CAAG,EAAA,YAAA,CAAa,QAAS,CAAA,CAAC,EAAE,MAAQ,EAAA,IAAI,CAAC,CAAA,CAC5D,KAAK,IAAI,CAAA;AACZ,MACE,uBAAA,GAAA;AAAA,QAAC,eAAA;AAAA,QAAA;AAAA,UACC,KAAA;AAAA,UACA,QAAA;AAAA,UACA,QACE,EAAA,CAAC,CAAC,YAAA,CAAa,OAAO,KACpB,oBAAA,GAAA;AAAA,YAAC,mBAAA;AAAA,YAAA;AAAA,cACC,SAAW,EAAA,YAAA;AAAA,cACX,QAAQ,YAAa,CAAA,MAAA;AAAA,cACrB,WAAW,OAAQ,CAAA;AAAA;AAAA,WACrB;AAAA,UAGJ,OACE,kBAAA,IAAA,CAAC,GAAI,EAAA,EAAA,SAAA,EAAW,QAAQ,OACtB,EAAA,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,cAAe,EAAA,EAAA,CAAA;AAAA,4BAChB,GAAA,CAAC,cAAW,QAAmB,EAAA,qBAAA,EAAA;AAAA,WACjC,EAAA,CAAA;AAAA,UAGD,QAAM,EAAA,KAAA,CAAA,GAAA,CAAI,CAAC,IAAA,EAAM,0BACf,GAAA,CAAA,mBAAA,EAAA,EAAmD,IAA1B,EAAA,EAAA,CAAA,EAAG,IAAK,CAAA,KAAK,CAAI,CAAA,EAAA,KAAK,EAAgB,CACjE;AAAA;AAAA,OACH;AAAA;AAKJ,IACE,uBAAA,GAAA;AAAA,MAAC,eAAA;AAAA,MAAA;AAAA,QACC,KAAA;AAAA,QACA,QACE,EAAA,CAAC,CAAC,YAAA,CAAa,OAAO,KACpB,oBAAA,GAAA;AAAA,UAAC,mBAAA;AAAA,UAAA;AAAA,YACC,SAAW,EAAA,YAAA;AAAA,YACX,QAAQ,YAAa,CAAA,MAAA;AAAA,YACrB,WAAW,OAAQ,CAAA;AAAA;AAAA,SACrB;AAAA,QAGJ,OAAA,EACE,KACI,IACA,GAAA,qGAAA;AAAA,QAGL,QAAM,EAAA,KAAA,CAAA,GAAA,CAAI,CAAC,IAAA,EAAM,0BACf,GAAA,CAAA,mBAAA,EAAA,EAAmD,IAA1B,EAAA,EAAA,CAAA,EAAG,IAAK,CAAA,KAAK,CAAI,CAAA,EAAA,KAAK,EAAgB,CACjE;AAAA;AAAA,KACH;AAAA,GAEJ;AAEA,EAAA,MAAM,aAAgB,GAAA,WAAA,GAAc,EAAE,OAAA,KAAY,EAAC;AAEnD,EAAA,uBACG,IAAA,CAAA,GAAA,EAAA,EAAI,SAAW,EAAA,aAAA,CAAc,OAC5B,EAAA,QAAA,EAAA;AAAA,oBAAC,GAAA,CAAA,cAAA,EAAA,EAAe,SAAsB,EAAA,OAAA,EAAkB,OACtD,EAAA,QAAA,kBAAA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,OAAO,MAAO,CAAA,CAAC,gBAAgB,aAAa,CAAA,EAAG,OAAO,MAAM,CAAA;AAAA,QAE5D,QAAC,kBAAA,GAAA,CAAA,UAAA,EAAA,EAAW,MAAQ,EAAA,MAAA,CAAO,QAAQ,QAAoB,EAAA;AAAA;AAAA,KAE3D,EAAA,CAAA;AAAA,oBACA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,SAAA;AAAA,QACA,OAAS,EAAA,4BAAA;AAAA,QACT,WAAA;AAAA,QACA,OAAA;AAAA,QACC,GAAG;AAAA;AAAA,KACN;AAAA,IACC,UAAA,IAAc,SAAS,MACtB,oBAAA,GAAA;AAAA,MAAC,mBAAA;AAAA,MAAA;AAAA,QACC,IAAM,EAAA,UAAA;AAAA,QACN,OAAA,EAAS,MAAM,WAAA,CAAY,KAAS,CAAA,CAAA;AAAA,QACpC,QAAQ,UAAW,CAAA,QAAA,EAAU,CAAK,CAAA,KAAA,CAAA,CAAE,OAAO,WAAW,CAAA;AAAA,QACtD;AAAA;AAAA;AACF,GAEJ,EAAA,CAAA;AAEJ;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import pluralize from 'pluralize';
|
|
3
3
|
import { ProjectGrowthAlertChart } from './ProjectGrowthAlertChart.esm.js';
|
|
4
4
|
import { InfoCard } from '@backstage/core-components';
|
|
@@ -6,13 +6,13 @@ import { InfoCard } from '@backstage/core-components';
|
|
|
6
6
|
const ProjectGrowthAlertCard = ({ alert }) => {
|
|
7
7
|
const subheader = `
|
|
8
8
|
${pluralize("product", alert.products.length, true)}${alert.products.length > 1 ? ", sorted by cost" : ""}`;
|
|
9
|
-
return /* @__PURE__ */
|
|
9
|
+
return /* @__PURE__ */ jsx(
|
|
10
10
|
InfoCard,
|
|
11
11
|
{
|
|
12
12
|
title: `Project growth for ${alert.project}`,
|
|
13
|
-
subheader
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
subheader,
|
|
14
|
+
children: /* @__PURE__ */ jsx(ProjectGrowthAlertChart, { alert })
|
|
15
|
+
}
|
|
16
16
|
);
|
|
17
17
|
};
|
|
18
18
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectGrowthAlertCard.esm.js","sources":["../../../src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport
|
|
1
|
+
{"version":3,"file":"ProjectGrowthAlertCard.esm.js","sources":["../../../src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport pluralize from 'pluralize';\nimport { ProjectGrowthAlertChart } from './ProjectGrowthAlertChart';\nimport { ProjectGrowthData } from '../../types';\nimport { InfoCard } from '@backstage/core-components';\n\ntype ProjectGrowthAlertProps = {\n alert: ProjectGrowthData;\n};\n\nexport const ProjectGrowthAlertCard = ({ alert }: ProjectGrowthAlertProps) => {\n const subheader = `\n ${pluralize('product', alert.products.length, true)}${\n alert.products.length > 1 ? ', sorted by cost' : ''\n }`;\n\n return (\n <InfoCard\n title={`Project growth for ${alert.project}`}\n subheader={subheader}\n >\n <ProjectGrowthAlertChart alert={alert} />\n </InfoCard>\n );\n};\n"],"names":[],"mappings":";;;;;AAyBO,MAAM,sBAAyB,GAAA,CAAC,EAAE,KAAA,EAAqC,KAAA;AAC5E,EAAA,MAAM,SAAY,GAAA;AAAA,IAAA,EACd,SAAU,CAAA,SAAA,EAAW,KAAM,CAAA,QAAA,CAAS,MAAQ,EAAA,IAAI,CAAC,CAAA,EACnD,KAAM,CAAA,QAAA,CAAS,MAAS,GAAA,CAAA,GAAI,qBAAqB,EACnD,CAAA,CAAA;AAEA,EACE,uBAAA,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAsB,mBAAA,EAAA,KAAA,CAAM,OAAO,CAAA,CAAA;AAAA,MAC1C,SAAA;AAAA,MAEA,QAAA,kBAAA,GAAA,CAAC,2BAAwB,KAAc,EAAA;AAAA;AAAA,GACzC;AAEJ;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
3
|
import Box from '@material-ui/core/Box';
|
|
4
4
|
import { BarChart } from '../BarChart/BarChart.esm.js';
|
|
@@ -39,7 +39,10 @@ const ProjectGrowthAlertChart = ({
|
|
|
39
39
|
"'Q'q yyyy"
|
|
40
40
|
)
|
|
41
41
|
};
|
|
42
|
-
return /* @__PURE__ */
|
|
42
|
+
return /* @__PURE__ */ jsxs(Box, { className: classes.wrapper, children: [
|
|
43
|
+
/* @__PURE__ */ jsx(BarChartLegend, { costStart, costEnd, options, children: /* @__PURE__ */ jsx(LegendItem, { title: "Cost Growth", children: /* @__PURE__ */ jsx(CostGrowth, { change: alert.change, duration: Duration.P3M }) }) }),
|
|
44
|
+
/* @__PURE__ */ jsx(BarChart, { resources: resourceData, options })
|
|
45
|
+
] });
|
|
43
46
|
};
|
|
44
47
|
|
|
45
48
|
export { ProjectGrowthAlertChart };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectGrowthAlertChart.esm.js","sources":["../../../src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport
|
|
1
|
+
{"version":3,"file":"ProjectGrowthAlertChart.esm.js","sources":["../../../src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DateTime } from 'luxon';\nimport Box from '@material-ui/core/Box';\nimport { BarChart, BarChartLegend, BarChartLegendOptions } from '../BarChart';\nimport { LegendItem } from '../LegendItem';\nimport { CostGrowth } from '../CostGrowth';\nimport { Duration, ProjectGrowthData } from '../../types';\nimport { useBarChartLayoutStyles as useStyles } from '../../utils/styles';\nimport { resourceOf } from '../../utils/graphs';\n\ntype ProjectGrowthAlertChartProps = {\n alert: ProjectGrowthData;\n};\n\nexport const ProjectGrowthAlertChart = ({\n alert,\n}: ProjectGrowthAlertChartProps) => {\n const classes = useStyles();\n\n const costStart = alert.aggregation[0];\n const costEnd = alert.aggregation[1];\n const resourceData = alert.products.map(resourceOf);\n\n const options: Partial<BarChartLegendOptions> = {\n previousName: DateTime.fromFormat(alert.periodStart, \"yyyy-'Q'q\").toFormat(\n \"'Q'q yyyy\",\n ),\n currentName: DateTime.fromFormat(alert.periodEnd, \"yyyy-'Q'q\").toFormat(\n \"'Q'q yyyy\",\n ),\n };\n\n return (\n <Box className={classes.wrapper}>\n <BarChartLegend costStart={costStart} costEnd={costEnd} options={options}>\n <LegendItem title=\"Cost Growth\">\n <CostGrowth change={alert.change} duration={Duration.P3M} />\n </LegendItem>\n </BarChartLegend>\n <BarChart resources={resourceData} options={options} />\n </Box>\n );\n};\n"],"names":["useStyles"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA6BO,MAAM,0BAA0B,CAAC;AAAA,EACtC;AACF,CAAoC,KAAA;AAClC,EAAA,MAAM,UAAUA,uBAAU,EAAA;AAE1B,EAAM,MAAA,SAAA,GAAY,KAAM,CAAA,WAAA,CAAY,CAAC,CAAA;AACrC,EAAM,MAAA,OAAA,GAAU,KAAM,CAAA,WAAA,CAAY,CAAC,CAAA;AACnC,EAAA,MAAM,YAAe,GAAA,KAAA,CAAM,QAAS,CAAA,GAAA,CAAI,UAAU,CAAA;AAElD,EAAA,MAAM,OAA0C,GAAA;AAAA,IAC9C,cAAc,QAAS,CAAA,UAAA,CAAW,KAAM,CAAA,WAAA,EAAa,WAAW,CAAE,CAAA,QAAA;AAAA,MAChE;AAAA,KACF;AAAA,IACA,aAAa,QAAS,CAAA,UAAA,CAAW,KAAM,CAAA,SAAA,EAAW,WAAW,CAAE,CAAA,QAAA;AAAA,MAC7D;AAAA;AACF,GACF;AAEA,EAAA,uBACG,IAAA,CAAA,GAAA,EAAA,EAAI,SAAW,EAAA,OAAA,CAAQ,OACtB,EAAA,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,kBAAe,SAAsB,EAAA,OAAA,EAAkB,OACtD,EAAA,QAAA,kBAAA,GAAA,CAAC,cAAW,KAAM,EAAA,aAAA,EAChB,QAAC,kBAAA,GAAA,CAAA,UAAA,EAAA,EAAW,QAAQ,KAAM,CAAA,MAAA,EAAQ,UAAU,QAAS,CAAA,GAAA,EAAK,GAC5D,CACF,EAAA,CAAA;AAAA,oBACC,GAAA,CAAA,QAAA,EAAA,EAAS,SAAW,EAAA,YAAA,EAAc,OAAkB,EAAA;AAAA,GACvD,EAAA,CAAA;AAEJ;;;;"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
3
|
import Box from '@material-ui/core/Box';
|
|
4
4
|
import Typography from '@material-ui/core/Typography';
|
|
5
5
|
import { AlertInstructionsLayout } from '../AlertInstructionsLayout/AlertInstructionsLayout.esm.js';
|
|
6
|
+
import 'react';
|
|
6
7
|
import 'pluralize';
|
|
7
8
|
import '@material-ui/lab/Alert';
|
|
8
9
|
import '@material-ui/core/MenuItem';
|
|
@@ -97,14 +98,59 @@ const ProjectGrowthInstructionsPage = () => {
|
|
|
97
98
|
]
|
|
98
99
|
}
|
|
99
100
|
};
|
|
100
|
-
return /* @__PURE__ */
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
101
|
+
return /* @__PURE__ */ jsxs(AlertInstructionsLayout, { title: "Investigating Growth", children: [
|
|
102
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h1", children: "Investigating cloud cost growth" }),
|
|
103
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "Cost Insights shows an alert when costs for a particular billing entity, such as a GCP project, have grown at a rate faster than our alerting threshold. The responsible team should follow this guide to decide whether this warrants further investigation." }),
|
|
104
|
+
/* @__PURE__ */ jsxs(Box, { mt: 4, children: [
|
|
105
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h3", children: "Is the growth expected?" }),
|
|
106
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "The first question to ask is whether growth is expected. Perhaps a new product has been deployed, or additional regions added for reliability." }),
|
|
107
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "Many services increase cost linearly with load. Has the demand increased? This may happen as you open new markets, or run marketing offers. Costs should be compared against a business metric, such as daily users, to normalize natural increases from business growth." }),
|
|
108
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "Seasonal variance may also cause cost growth; yearly campaigns, an increase in demand during certain times of year." }),
|
|
109
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "Cloud costs will often go up before they go down, in the case of migrations. Teams moving to new infrastructure may run in both the old and new environment during the migration." })
|
|
110
|
+
] }),
|
|
111
|
+
/* @__PURE__ */ jsxs(Box, { mt: 4, children: [
|
|
112
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h3", children: "Is the growth significant?" }),
|
|
113
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "Next, evaluate whether the growth is significant. This helps avoid premature optimization, where cost in engineering time is more than would be saved from the optimization over a reasonable time frame." }),
|
|
114
|
+
/* @__PURE__ */ jsxs(Typography, { paragraph: true, children: [
|
|
115
|
+
"We recommend reframing the cost growth itself in terms of engineering time. How much engineering time, for an ",
|
|
116
|
+
/* @__PURE__ */ jsx("i", { children: "average" }),
|
|
117
|
+
" fully-loaded engineer cost at the company, is being overspent each month? Compare this to expected engineering time for optimization to decide whether the optimization is worthwhile."
|
|
118
|
+
] })
|
|
119
|
+
] }),
|
|
120
|
+
/* @__PURE__ */ jsxs(Box, { mt: 4, children: [
|
|
121
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h3", children: "Identifying which cloud product contributed most" }),
|
|
122
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "For projects meeting the alert threshold, Cost Insights shows a cost comparison of cloud products over the examined time period:" }),
|
|
123
|
+
/* @__PURE__ */ jsx(Box, { mt: 2, mb: 2, children: projectGrowthAlert.element }),
|
|
124
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "This allows you to quickly see which cloud products contributed to the growth in cloud costs." })
|
|
125
|
+
] }),
|
|
126
|
+
/* @__PURE__ */ jsxs(Box, { mt: 4, children: [
|
|
127
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h3", children: "Identifying the responsible workload" }),
|
|
128
|
+
/* @__PURE__ */ jsxs(Typography, { paragraph: true, children: [
|
|
129
|
+
"After identifying the cloud product, use the corresponding product panel in Cost Insights to find a particular workload (or ",
|
|
130
|
+
/* @__PURE__ */ jsx("i", { children: "entity" }),
|
|
131
|
+
") that has grown in cost:"
|
|
132
|
+
] }),
|
|
133
|
+
/* @__PURE__ */ jsx(Box, { mt: 2, mb: 2, children: /* @__PURE__ */ jsx(InfoCard, { title: product.name, subheader: "3 entities, sorted by cost", children: /* @__PURE__ */ jsx(
|
|
134
|
+
ProductInsightsChart,
|
|
135
|
+
{
|
|
136
|
+
billingDate: today,
|
|
137
|
+
duration: Duration.P3M,
|
|
138
|
+
entity
|
|
139
|
+
}
|
|
140
|
+
) }) }),
|
|
141
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "From here, you can dig into commit history or deployment logs to find probable causes of an unexpected spike in cost." })
|
|
142
|
+
] }),
|
|
143
|
+
/* @__PURE__ */ jsxs(Box, { mt: 4, children: [
|
|
144
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h3", children: "Optimizing the workload" }),
|
|
145
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "Workload optimization varies between cloud products, but there are a few general optimization areas to consider:" }),
|
|
146
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h5", children: "Retention" }),
|
|
147
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "Is the workload or storage necessary? Truly idle or unused resources can be cleaned up for immediate cost savings. For storage, how long do we need the data? Many cloud products support retention policies to automatically delete data after a certain time period." }),
|
|
148
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h5", children: "Efficiency" }),
|
|
149
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "Is the workload using cloud resources efficiently? For compute resources, do the utilization metrics look reasonable? Autoscaling infrastructure, such as Kubernetes, can run workloads more efficiently without compromising reliability." }),
|
|
150
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h5", children: "Lifecycle" }),
|
|
151
|
+
/* @__PURE__ */ jsx(Typography, { paragraph: true, children: "Is the workload using an optimal pricing model? Some cloud products offer better pricing for data that is accessed less frequently." })
|
|
152
|
+
] })
|
|
153
|
+
] });
|
|
108
154
|
};
|
|
109
155
|
|
|
110
156
|
export { ProjectGrowthInstructionsPage };
|