@backstage-community/plugin-cost-insights 0.12.24 → 0.12.26
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 +14 -0
- package/README.md +6 -6
- package/dist/alerts/ProjectGrowthAlert.esm.js +24 -0
- package/dist/alerts/ProjectGrowthAlert.esm.js.map +1 -0
- package/dist/alerts/UnlabeledDataflowAlert.esm.js +25 -0
- package/dist/alerts/UnlabeledDataflowAlert.esm.js.map +1 -0
- package/dist/api/CostInsightsApi.esm.js +8 -0
- package/dist/api/CostInsightsApi.esm.js.map +1 -0
- package/dist/components/ActionItems/ActionItemCard.esm.js +46 -0
- package/dist/components/ActionItems/ActionItemCard.esm.js.map +1 -0
- package/dist/components/ActionItems/ActionItems.esm.js +90 -0
- package/dist/components/ActionItems/ActionItems.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertDialog.esm.js +123 -0
- package/dist/components/AlertInsights/AlertDialog.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertInsights.esm.js +157 -0
- package/dist/components/AlertInsights/AlertInsights.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertInsightsHeader.esm.js +17 -0
- package/dist/components/AlertInsights/AlertInsightsHeader.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertInsightsSection.esm.js +57 -0
- package/dist/components/AlertInsights/AlertInsightsSection.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertInsightsSectionHeader.esm.js +30 -0
- package/dist/components/AlertInsights/AlertInsightsSectionHeader.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertStatusSummary.esm.js +93 -0
- package/dist/components/AlertInsights/AlertStatusSummary.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertStatusSummaryButton.esm.js +35 -0
- package/dist/components/AlertInsights/AlertStatusSummaryButton.esm.js.map +1 -0
- package/dist/components/AlertInstructionsLayout/AlertInstructionsLayout.esm.js +47 -0
- package/dist/components/AlertInstructionsLayout/AlertInstructionsLayout.esm.js.map +1 -0
- package/dist/components/BarChart/BarChart.esm.js +167 -0
- package/dist/components/BarChart/BarChart.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartLabel.esm.js +31 -0
- package/dist/components/BarChart/BarChartLabel.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartLegend.esm.js +47 -0
- package/dist/components/BarChart/BarChartLegend.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartStepper.esm.js +91 -0
- package/dist/components/BarChart/BarChartStepper.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartStepperButton.esm.js +27 -0
- package/dist/components/BarChart/BarChartStepperButton.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartSteps.esm.js +25 -0
- package/dist/components/BarChart/BarChartSteps.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartTick.esm.js +29 -0
- package/dist/components/BarChart/BarChartTick.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartTooltip.esm.js +30 -0
- package/dist/components/BarChart/BarChartTooltip.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartTooltipItem.esm.js +24 -0
- package/dist/components/BarChart/BarChartTooltipItem.esm.js.map +1 -0
- package/dist/components/CopyUrlToClipboard/CopyUrlToClipboard.esm.js +44 -0
- package/dist/components/CopyUrlToClipboard/CopyUrlToClipboard.esm.js.map +1 -0
- package/dist/components/CostGrowth/CostGrowth.esm.js +55 -0
- package/dist/components/CostGrowth/CostGrowth.esm.js.map +1 -0
- package/dist/components/CostGrowth/CostGrowthIndicator.esm.js +31 -0
- package/dist/components/CostGrowth/CostGrowthIndicator.esm.js.map +1 -0
- package/dist/components/CostInsightsHeader/CostInsightsHeader.esm.js +57 -0
- package/dist/components/CostInsightsHeader/CostInsightsHeader.esm.js.map +1 -0
- package/dist/components/CostInsightsLayout/CostInsightsLayout.esm.js +35 -0
- package/dist/components/CostInsightsLayout/CostInsightsLayout.esm.js.map +1 -0
- package/dist/components/CostInsightsNavigation/CostInsightsNavigation.esm.js +87 -0
- package/dist/components/CostInsightsNavigation/CostInsightsNavigation.esm.js.map +1 -0
- package/dist/components/CostInsightsPage/CostInsightsPage.esm.js +229 -0
- package/dist/components/CostInsightsPage/CostInsightsPage.esm.js.map +1 -0
- package/dist/components/CostInsightsPage/CostInsightsPageRoot.esm.js +15 -0
- package/dist/components/CostInsightsPage/CostInsightsPageRoot.esm.js.map +1 -0
- package/dist/components/CostInsightsPage/CostInsightsThemeProvider.esm.js +24 -0
- package/dist/components/CostInsightsPage/CostInsightsThemeProvider.esm.js.map +1 -0
- package/dist/components/CostInsightsPage/index.esm.js +2 -0
- package/dist/components/CostInsightsPage/index.esm.js.map +1 -0
- package/dist/components/CostInsightsPage/selector.esm.js +15 -0
- package/dist/components/CostInsightsPage/selector.esm.js.map +1 -0
- package/dist/components/CostInsightsTabs/CostInsightsTabs.esm.js +89 -0
- package/dist/components/CostInsightsTabs/CostInsightsTabs.esm.js.map +1 -0
- package/dist/components/CostInsightsTabs/selector.esm.js +17 -0
- package/dist/components/CostInsightsTabs/selector.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js +196 -0
- package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/CostOverviewCard.esm.js +99 -0
- package/dist/components/CostOverviewCard/CostOverviewCard.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/CostOverviewChart.esm.js +157 -0
- package/dist/components/CostOverviewCard/CostOverviewChart.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/CostOverviewHeader.esm.js +25 -0
- package/dist/components/CostOverviewCard/CostOverviewHeader.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js +54 -0
- package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/selector.esm.js +22 -0
- package/dist/components/CostOverviewCard/selector.esm.js.map +1 -0
- package/dist/components/CurrencySelect/CurrencySelect.esm.js +52 -0
- package/dist/components/CurrencySelect/CurrencySelect.esm.js.map +1 -0
- package/dist/{esm/index-BPzWHt3A.esm.js → components/EntityCosts/EntityCosts.esm.js} +14 -80
- package/dist/{esm/index-BPzWHt3A.esm.js.map → components/EntityCosts/EntityCosts.esm.js.map} +1 -1
- package/dist/components/EntityCosts/index.esm.js +2 -0
- package/dist/components/EntityCosts/index.esm.js.map +1 -0
- package/dist/{esm/index-BA6m_qFS.esm.js → components/LabelDataflowInstructionsPage/LabelDataflowInstructionsPage.esm.js} +2 -79
- package/dist/components/LabelDataflowInstructionsPage/LabelDataflowInstructionsPage.esm.js.map +1 -0
- package/dist/components/LabelDataflowInstructionsPage/index.esm.js +2 -0
- package/dist/components/LabelDataflowInstructionsPage/index.esm.js.map +1 -0
- package/dist/components/LegendItem/LegendItem.esm.js +44 -0
- package/dist/components/LegendItem/LegendItem.esm.js.map +1 -0
- package/dist/components/MetricSelect/MetricSelect.esm.js +37 -0
- package/dist/components/MetricSelect/MetricSelect.esm.js.map +1 -0
- package/dist/components/PeriodSelect/PeriodSelect.esm.js +72 -0
- package/dist/components/PeriodSelect/PeriodSelect.esm.js.map +1 -0
- package/dist/components/ProductInsights/ProductInsights.esm.js +98 -0
- package/dist/components/ProductInsights/ProductInsights.esm.js.map +1 -0
- package/dist/components/ProductInsightsCard/ProductEntityDialog.esm.js +42 -0
- package/dist/components/ProductInsightsCard/ProductEntityDialog.esm.js.map +1 -0
- package/dist/components/ProductInsightsCard/ProductEntityTable.esm.js +139 -0
- package/dist/components/ProductInsightsCard/ProductEntityTable.esm.js.map +1 -0
- package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js +83 -0
- package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js.map +1 -0
- package/dist/components/ProductInsightsCard/ProductInsightsCardList.esm.js +34 -0
- package/dist/components/ProductInsightsCard/ProductInsightsCardList.esm.js.map +1 -0
- package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js +148 -0
- package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js.map +1 -0
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.esm.js +20 -0
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.esm.js.map +1 -0
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.esm.js +46 -0
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.esm.js.map +1 -0
- package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js +111 -0
- package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js.map +1 -0
- package/dist/components/ProjectGrowthInstructionsPage/index.esm.js +2 -0
- package/dist/components/ProjectGrowthInstructionsPage/index.esm.js.map +1 -0
- package/dist/components/ProjectSelect/ProjectSelect.esm.js +47 -0
- package/dist/components/ProjectSelect/ProjectSelect.esm.js.map +1 -0
- package/dist/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.esm.js +41 -0
- package/dist/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.esm.js.map +1 -0
- package/dist/components/WhyCostsMatter/WhyCostsMatter.esm.js +21 -0
- package/dist/components/WhyCostsMatter/WhyCostsMatter.esm.js.map +1 -0
- package/dist/example/alerts/KubernetesMigrationAlert.esm.js +86 -0
- package/dist/example/alerts/KubernetesMigrationAlert.esm.js.map +1 -0
- package/dist/example/client.esm.js +194 -0
- package/dist/example/client.esm.js.map +1 -0
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.esm.js +33 -0
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.esm.js.map +1 -0
- package/dist/{esm/index-BTIHr4n_.esm.js → example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js} +69 -48
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js.map +1 -0
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js +107 -0
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js.map +1 -0
- package/dist/example/forms/KubernetesMigrationDismissForm.esm.js +57 -0
- package/dist/example/forms/KubernetesMigrationDismissForm.esm.js.map +1 -0
- package/dist/forms/AlertAcceptForm.esm.js +27 -0
- package/dist/forms/AlertAcceptForm.esm.js.map +1 -0
- package/dist/forms/AlertDismissForm.esm.js +100 -0
- package/dist/forms/AlertDismissForm.esm.js.map +1 -0
- package/dist/forms/AlertSnoozeForm.esm.js +52 -0
- package/dist/forms/AlertSnoozeForm.esm.js.map +1 -0
- package/dist/hooks/useConfig.esm.js +153 -0
- package/dist/hooks/useConfig.esm.js.map +1 -0
- package/dist/hooks/useCurrency.esm.js +26 -0
- package/dist/hooks/useCurrency.esm.js.map +1 -0
- package/dist/hooks/useFilters.esm.js +67 -0
- package/dist/hooks/useFilters.esm.js.map +1 -0
- package/dist/hooks/useGroups.esm.js +55 -0
- package/dist/hooks/useGroups.esm.js.map +1 -0
- package/dist/hooks/useLastCompleteBillingDate.esm.js +65 -0
- package/dist/hooks/useLastCompleteBillingDate.esm.js.map +1 -0
- package/dist/hooks/useLoading.esm.js +54 -0
- package/dist/hooks/useLoading.esm.js.map +1 -0
- package/dist/hooks/useScroll.esm.js +24 -0
- package/dist/hooks/useScroll.esm.js.map +1 -0
- package/dist/index.esm.js +37 -22
- package/dist/index.esm.js.map +1 -1
- package/dist/plugin.esm.js +55 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/testUtils/filters.esm.js +8 -0
- package/dist/testUtils/filters.esm.js.map +1 -0
- package/dist/testUtils/loading.esm.js +14 -0
- package/dist/testUtils/loading.esm.js.map +1 -0
- package/dist/testUtils/mockData.esm.js +485 -0
- package/dist/testUtils/mockData.esm.js.map +1 -0
- package/dist/testUtils/products.esm.js +49 -0
- package/dist/testUtils/products.esm.js.map +1 -0
- package/dist/testUtils/providers.esm.js +41 -0
- package/dist/testUtils/providers.esm.js.map +1 -0
- package/dist/testUtils/testUtils.esm.js +142 -0
- package/dist/testUtils/testUtils.esm.js.map +1 -0
- package/dist/types/Alert.esm.js +66 -0
- package/dist/types/Alert.esm.js.map +1 -0
- package/dist/types/ChangeStatistic.esm.js +15 -0
- package/dist/types/ChangeStatistic.esm.js.map +1 -0
- package/dist/types/CurrencyType.esm.js +10 -0
- package/dist/types/CurrencyType.esm.js.map +1 -0
- package/dist/types/DateFormat.esm.js +4 -0
- package/dist/types/DateFormat.esm.js.map +1 -0
- package/dist/types/Duration.esm.js +10 -0
- package/dist/types/Duration.esm.js.map +1 -0
- package/dist/types/Icon.esm.js +12 -0
- package/dist/types/Icon.esm.js.map +1 -0
- package/dist/utils/alerts.esm.js +72 -0
- package/dist/utils/alerts.esm.js.map +1 -0
- package/dist/utils/assert.esm.js +29 -0
- package/dist/utils/assert.esm.js.map +1 -0
- package/dist/utils/change.esm.js +58 -0
- package/dist/utils/change.esm.js.map +1 -0
- package/dist/utils/charts.esm.js +9 -0
- package/dist/utils/charts.esm.js.map +1 -0
- package/dist/utils/config.esm.js +19 -0
- package/dist/utils/config.esm.js.map +1 -0
- package/dist/utils/currency.esm.js +56 -0
- package/dist/utils/currency.esm.js.map +1 -0
- package/dist/utils/duration.esm.js +55 -0
- package/dist/utils/duration.esm.js.map +1 -0
- package/dist/utils/filters.esm.js +14 -0
- package/dist/utils/filters.esm.js.map +1 -0
- package/dist/utils/formatters.esm.js +103 -0
- package/dist/utils/formatters.esm.js.map +1 -0
- package/dist/utils/grammar.esm.js +14 -0
- package/dist/utils/grammar.esm.js.map +1 -0
- package/dist/utils/graphs.esm.js +48 -0
- package/dist/utils/graphs.esm.js.map +1 -0
- package/dist/utils/history.esm.js +32 -0
- package/dist/utils/history.esm.js.map +1 -0
- package/dist/utils/loading.esm.js +51 -0
- package/dist/utils/loading.esm.js.map +1 -0
- package/dist/utils/navigation.esm.js +56 -0
- package/dist/utils/navigation.esm.js.map +1 -0
- package/dist/utils/scroll.esm.js +38 -0
- package/dist/utils/scroll.esm.js.map +1 -0
- package/dist/utils/sort.esm.js +10 -0
- package/dist/utils/sort.esm.js.map +1 -0
- package/dist/utils/styles.esm.js +562 -0
- package/dist/utils/styles.esm.js.map +1 -0
- package/dist/utils/sum.esm.js +4 -0
- package/dist/utils/sum.esm.js.map +1 -0
- package/package.json +17 -12
- package/dist/esm/index-BA6m_qFS.esm.js.map +0 -1
- package/dist/esm/index-BFM2mP6Z.esm.js +0 -4084
- package/dist/esm/index-BFM2mP6Z.esm.js.map +0 -1
- package/dist/esm/index-BTIHr4n_.esm.js.map +0 -1
- package/dist/esm/index-DaitkcPN.esm.js +0 -1136
- package/dist/esm/index-DaitkcPN.esm.js.map +0 -1
- package/dist/esm/selector-D7iXJwd9.esm.js +0 -594
- package/dist/esm/selector-D7iXJwd9.esm.js.map +0 -1
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import pluralize from 'pluralize';
|
|
3
|
+
import { ProjectGrowthAlertChart } from './ProjectGrowthAlertChart.esm.js';
|
|
4
|
+
import { InfoCard } from '@backstage/core-components';
|
|
5
|
+
|
|
6
|
+
const ProjectGrowthAlertCard = ({ alert }) => {
|
|
7
|
+
const subheader = `
|
|
8
|
+
${pluralize("product", alert.products.length, true)}${alert.products.length > 1 ? ", sorted by cost" : ""}`;
|
|
9
|
+
return /* @__PURE__ */ React.createElement(
|
|
10
|
+
InfoCard,
|
|
11
|
+
{
|
|
12
|
+
title: `Project growth for ${alert.project}`,
|
|
13
|
+
subheader
|
|
14
|
+
},
|
|
15
|
+
/* @__PURE__ */ React.createElement(ProjectGrowthAlertChart, { alert })
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export { ProjectGrowthAlertCard };
|
|
20
|
+
//# sourceMappingURL=ProjectGrowthAlertCard.esm.js.map
|
|
@@ -0,0 +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 React from 'react';\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":";;;;;AA0BO,MAAM,sBAAyB,GAAA,CAAC,EAAE,KAAA,EAAqC,KAAA;AAC5E,EAAA,MAAM,SAAY,GAAA,CAAA;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,CAAA;AAEA,EACE,uBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAsB,mBAAA,EAAA,KAAA,CAAM,OAAO,CAAA,CAAA;AAAA,MAC1C,SAAA;AAAA,KAAA;AAAA,oBAEA,KAAA,CAAA,aAAA,CAAC,2BAAwB,KAAc,EAAA,CAAA;AAAA,GACzC,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DateTime } from 'luxon';
|
|
3
|
+
import Box from '@material-ui/core/Box';
|
|
4
|
+
import { BarChart } from '../BarChart/BarChart.esm.js';
|
|
5
|
+
import { BarChartLegend } from '../BarChart/BarChartLegend.esm.js';
|
|
6
|
+
import 'classnames';
|
|
7
|
+
import '@material-ui/core/Divider';
|
|
8
|
+
import '@material-ui/core/Typography';
|
|
9
|
+
import { useBarChartLayoutStyles } from '../../utils/styles.esm.js';
|
|
10
|
+
import '@material-ui/icons/Lens';
|
|
11
|
+
import { LegendItem } from '../LegendItem/LegendItem.esm.js';
|
|
12
|
+
import { CostGrowth } from '../CostGrowth/CostGrowth.esm.js';
|
|
13
|
+
import '@material-ui/icons/ArrowDropUp';
|
|
14
|
+
import '@material-ui/icons/ArrowDropDown';
|
|
15
|
+
import '../../types/Alert.esm.js';
|
|
16
|
+
import { Duration } from '../../types/Duration.esm.js';
|
|
17
|
+
import '../../utils/duration.esm.js';
|
|
18
|
+
import '../../hooks/useConfig.esm.js';
|
|
19
|
+
import '../../hooks/useCurrency.esm.js';
|
|
20
|
+
import '../../hooks/useFilters.esm.js';
|
|
21
|
+
import '../../hooks/useGroups.esm.js';
|
|
22
|
+
import '../../hooks/useLoading.esm.js';
|
|
23
|
+
import '../../hooks/useScroll.esm.js';
|
|
24
|
+
import '../../hooks/useLastCompleteBillingDate.esm.js';
|
|
25
|
+
import { resourceOf } from '../../utils/graphs.esm.js';
|
|
26
|
+
|
|
27
|
+
const ProjectGrowthAlertChart = ({
|
|
28
|
+
alert
|
|
29
|
+
}) => {
|
|
30
|
+
const classes = useBarChartLayoutStyles();
|
|
31
|
+
const costStart = alert.aggregation[0];
|
|
32
|
+
const costEnd = alert.aggregation[1];
|
|
33
|
+
const resourceData = alert.products.map(resourceOf);
|
|
34
|
+
const options = {
|
|
35
|
+
previousName: DateTime.fromFormat(alert.periodStart, "yyyy-'Q'q").toFormat(
|
|
36
|
+
"'Q'q yyyy"
|
|
37
|
+
),
|
|
38
|
+
currentName: DateTime.fromFormat(alert.periodEnd, "yyyy-'Q'q").toFormat(
|
|
39
|
+
"'Q'q yyyy"
|
|
40
|
+
)
|
|
41
|
+
};
|
|
42
|
+
return /* @__PURE__ */ React.createElement(Box, { className: classes.wrapper }, /* @__PURE__ */ React.createElement(BarChartLegend, { costStart, costEnd, options }, /* @__PURE__ */ React.createElement(LegendItem, { title: "Cost Growth" }, /* @__PURE__ */ React.createElement(CostGrowth, { change: alert.change, duration: Duration.P3M }))), /* @__PURE__ */ React.createElement(BarChart, { resources: resourceData, options }));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export { ProjectGrowthAlertChart };
|
|
46
|
+
//# sourceMappingURL=ProjectGrowthAlertChart.esm.js.map
|
|
@@ -0,0 +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 React from 'react';\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":";;;;;;;;;;;;;;;;;;;;;;;;;;AA8BO,MAAM,0BAA0B,CAAC;AAAA,EACtC,KAAA;AACF,CAAoC,KAAA;AAClC,EAAA,MAAM,UAAUA,uBAAU,EAAA,CAAA;AAE1B,EAAM,MAAA,SAAA,GAAY,KAAM,CAAA,WAAA,CAAY,CAAC,CAAA,CAAA;AACrC,EAAM,MAAA,OAAA,GAAU,KAAM,CAAA,WAAA,CAAY,CAAC,CAAA,CAAA;AACnC,EAAA,MAAM,YAAe,GAAA,KAAA,CAAM,QAAS,CAAA,GAAA,CAAI,UAAU,CAAA,CAAA;AAElD,EAAA,MAAM,OAA0C,GAAA;AAAA,IAC9C,cAAc,QAAS,CAAA,UAAA,CAAW,KAAM,CAAA,WAAA,EAAa,WAAW,CAAE,CAAA,QAAA;AAAA,MAChE,WAAA;AAAA,KACF;AAAA,IACA,aAAa,QAAS,CAAA,UAAA,CAAW,KAAM,CAAA,SAAA,EAAW,WAAW,CAAE,CAAA,QAAA;AAAA,MAC7D,WAAA;AAAA,KACF;AAAA,GACF,CAAA;AAEA,EAAA,uBACG,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA,EAAI,SAAW,EAAA,OAAA,CAAQ,OACtB,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,cAAe,EAAA,EAAA,SAAA,EAAsB,OAAkB,EAAA,OAAA,EAAA,kBACrD,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,OAAM,aAChB,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,UAAW,EAAA,EAAA,MAAA,EAAQ,KAAM,CAAA,MAAA,EAAQ,QAAU,EAAA,QAAA,CAAS,GAAK,EAAA,CAC5D,CACF,CAAA,kBACC,KAAA,CAAA,aAAA,CAAA,QAAA,EAAA,EAAS,SAAW,EAAA,YAAA,EAAc,SAAkB,CACvD,CAAA,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DateTime } from 'luxon';
|
|
3
|
+
import Box from '@material-ui/core/Box';
|
|
4
|
+
import Typography from '@material-ui/core/Typography';
|
|
5
|
+
import { AlertInstructionsLayout } from '../AlertInstructionsLayout/AlertInstructionsLayout.esm.js';
|
|
6
|
+
import 'pluralize';
|
|
7
|
+
import '@material-ui/lab/Alert';
|
|
8
|
+
import '@material-ui/core/MenuItem';
|
|
9
|
+
import '@material-ui/core/Select';
|
|
10
|
+
import '../../types/Alert.esm.js';
|
|
11
|
+
import { DEFAULT_DATE_FORMAT } from '../../types/DateFormat.esm.js';
|
|
12
|
+
import { Duration } from '../../types/Duration.esm.js';
|
|
13
|
+
import '../../utils/duration.esm.js';
|
|
14
|
+
import '../../utils/styles.esm.js';
|
|
15
|
+
import '../../hooks/useConfig.esm.js';
|
|
16
|
+
import '../../hooks/useCurrency.esm.js';
|
|
17
|
+
import '../../hooks/useFilters.esm.js';
|
|
18
|
+
import '../../hooks/useGroups.esm.js';
|
|
19
|
+
import '../../hooks/useLoading.esm.js';
|
|
20
|
+
import '../../hooks/useScroll.esm.js';
|
|
21
|
+
import '../../hooks/useLastCompleteBillingDate.esm.js';
|
|
22
|
+
import { ProductInsightsChart } from '../ProductInsightsCard/ProductInsightsChart.esm.js';
|
|
23
|
+
import { InfoCard } from '@backstage/core-components';
|
|
24
|
+
import { ProjectGrowthAlert } from '../../alerts/ProjectGrowthAlert.esm.js';
|
|
25
|
+
import 'recharts';
|
|
26
|
+
import '@material-ui/core/styles';
|
|
27
|
+
import '@material-ui/core/Paper';
|
|
28
|
+
import '@material-ui/core/Slide';
|
|
29
|
+
import '@material-ui/icons/ChevronLeft';
|
|
30
|
+
import '@material-ui/icons/ChevronRight';
|
|
31
|
+
import '../BarChart/BarChartStepperButton.esm.js';
|
|
32
|
+
import '@material-ui/core/ButtonBase';
|
|
33
|
+
import 'classnames';
|
|
34
|
+
import '@material-ui/core/Divider';
|
|
35
|
+
import '@material-ui/icons/Lens';
|
|
36
|
+
import '@material-ui/core/Tooltip';
|
|
37
|
+
import '@material-ui/icons/HelpOutlineOutlined';
|
|
38
|
+
|
|
39
|
+
const today = DateTime.now().toFormat(DEFAULT_DATE_FORMAT);
|
|
40
|
+
const ProjectGrowthInstructionsPage = () => {
|
|
41
|
+
const alertData = {
|
|
42
|
+
project: "example-project",
|
|
43
|
+
periodStart: "Q1 2020",
|
|
44
|
+
periodEnd: "Q2 2020",
|
|
45
|
+
aggregation: [6e4, 12e4],
|
|
46
|
+
change: {
|
|
47
|
+
ratio: 1,
|
|
48
|
+
amount: 6e4
|
|
49
|
+
},
|
|
50
|
+
products: [
|
|
51
|
+
{
|
|
52
|
+
id: "Compute Engine",
|
|
53
|
+
aggregation: [58e3, 118e3]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "Cloud Dataflow",
|
|
57
|
+
aggregation: [1200, 1500]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "Cloud Storage",
|
|
61
|
+
aggregation: [800, 500]
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
const projectGrowthAlert = new ProjectGrowthAlert(alertData);
|
|
66
|
+
const product = {
|
|
67
|
+
kind: "ComputeEngine",
|
|
68
|
+
name: "Compute Engine"
|
|
69
|
+
};
|
|
70
|
+
const entity = {
|
|
71
|
+
id: "example-id",
|
|
72
|
+
aggregation: [2e4, 6e4],
|
|
73
|
+
change: {
|
|
74
|
+
ratio: 3,
|
|
75
|
+
amount: 4e4
|
|
76
|
+
},
|
|
77
|
+
entities: {
|
|
78
|
+
service: [
|
|
79
|
+
{
|
|
80
|
+
id: "service-one",
|
|
81
|
+
aggregation: [18200, 58500],
|
|
82
|
+
entities: {},
|
|
83
|
+
change: { ratio: 2.21, amount: 40300 }
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "service-two",
|
|
87
|
+
aggregation: [1200, 1300],
|
|
88
|
+
entities: {},
|
|
89
|
+
change: { ratio: 0.083, amount: 100 }
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "service-three",
|
|
93
|
+
aggregation: [600, 200],
|
|
94
|
+
entities: {},
|
|
95
|
+
change: { ratio: -0.666, amount: -400 }
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
return /* @__PURE__ */ React.createElement(AlertInstructionsLayout, { title: "Investigating Growth" }, /* @__PURE__ */ React.createElement(Typography, { variant: "h1" }, "Investigating cloud cost growth"), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "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."), /* @__PURE__ */ React.createElement(Box, { mt: 4 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h3" }, "Is the growth expected?"), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "The first question to ask is whether growth is expected. Perhaps a new product has been deployed, or additional regions added for reliability."), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "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."), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "Seasonal variance may also cause cost growth; yearly campaigns, an increase in demand during certain times of year."), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "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.")), /* @__PURE__ */ React.createElement(Box, { mt: 4 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h3" }, "Is the growth significant?"), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "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."), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "We recommend reframing the cost growth itself in terms of engineering time. How much engineering time, for an ", /* @__PURE__ */ React.createElement("i", null, "average"), " 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.")), /* @__PURE__ */ React.createElement(Box, { mt: 4 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h3" }, "Identifying which cloud product contributed most"), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "For projects meeting the alert threshold, Cost Insights shows a cost comparison of cloud products over the examined time period:"), /* @__PURE__ */ React.createElement(Box, { mt: 2, mb: 2 }, projectGrowthAlert.element), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "This allows you to quickly see which cloud products contributed to the growth in cloud costs.")), /* @__PURE__ */ React.createElement(Box, { mt: 4 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h3" }, "Identifying the responsible workload"), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "After identifying the cloud product, use the corresponding product panel in Cost Insights to find a particular workload (or ", /* @__PURE__ */ React.createElement("i", null, "entity"), ") that has grown in cost:"), /* @__PURE__ */ React.createElement(Box, { mt: 2, mb: 2 }, /* @__PURE__ */ React.createElement(InfoCard, { title: product.name, subheader: "3 entities, sorted by cost" }, /* @__PURE__ */ React.createElement(
|
|
101
|
+
ProductInsightsChart,
|
|
102
|
+
{
|
|
103
|
+
billingDate: today,
|
|
104
|
+
duration: Duration.P3M,
|
|
105
|
+
entity
|
|
106
|
+
}
|
|
107
|
+
))), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "From here, you can dig into commit history or deployment logs to find probable causes of an unexpected spike in cost.")), /* @__PURE__ */ React.createElement(Box, { mt: 4 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h3" }, "Optimizing the workload"), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "Workload optimization varies between cloud products, but there are a few general optimization areas to consider:"), /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, "Retention"), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "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."), /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, "Efficiency"), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "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."), /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, "Lifecycle"), /* @__PURE__ */ React.createElement(Typography, { paragraph: true }, "Is the workload using an optimal pricing model? Some cloud products offer better pricing for data that is accessed less frequently.")));
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export { ProjectGrowthInstructionsPage };
|
|
111
|
+
//# sourceMappingURL=ProjectGrowthInstructionsPage.esm.js.map
|
package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectGrowthInstructionsPage.esm.js","sources":["../../../src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.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 from 'react';\nimport { DateTime } from 'luxon';\nimport Box from '@material-ui/core/Box';\nimport Typography from '@material-ui/core/Typography';\nimport { AlertInstructionsLayout } from '../AlertInstructionsLayout';\nimport { ProductInsightsChart } from '../ProductInsightsCard';\nimport {\n Alert,\n DEFAULT_DATE_FORMAT,\n Duration,\n ProjectGrowthData,\n} from '../../types';\nimport {\n Entity,\n Product,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { ProjectGrowthAlert } from '../../alerts';\nimport { InfoCard } from '@backstage/core-components';\n\nconst today = DateTime.now().toFormat(DEFAULT_DATE_FORMAT);\n\nexport const ProjectGrowthInstructionsPage = () => {\n const alertData: ProjectGrowthData = {\n project: 'example-project',\n periodStart: 'Q1 2020',\n periodEnd: 'Q2 2020',\n aggregation: [60000, 120000],\n change: {\n ratio: 1,\n amount: 60000,\n },\n products: [\n {\n id: 'Compute Engine',\n aggregation: [58000, 118000],\n },\n {\n id: 'Cloud Dataflow',\n aggregation: [1200, 1500],\n },\n {\n id: 'Cloud Storage',\n aggregation: [800, 500],\n },\n ],\n };\n\n const projectGrowthAlert: Alert = new ProjectGrowthAlert(alertData);\n\n const product: Product = {\n kind: 'ComputeEngine',\n name: 'Compute Engine',\n };\n\n const entity: Entity = {\n id: 'example-id',\n aggregation: [20_000, 60_000],\n change: {\n ratio: 3,\n amount: 40_000,\n },\n entities: {\n service: [\n {\n id: 'service-one',\n aggregation: [18_200, 58_500],\n entities: {},\n change: { ratio: 2.21, amount: 40_300 },\n },\n {\n id: 'service-two',\n aggregation: [1200, 1300],\n entities: {},\n change: { ratio: 0.083, amount: 100 },\n },\n {\n id: 'service-three',\n aggregation: [600, 200],\n entities: {},\n change: { ratio: -0.666, amount: -400 },\n },\n ],\n },\n };\n\n return (\n <AlertInstructionsLayout title=\"Investigating Growth\">\n <Typography variant=\"h1\">Investigating cloud cost growth</Typography>\n <Typography paragraph>\n Cost Insights shows an alert when costs for a particular billing entity,\n such as a GCP project, have grown at a rate faster than our alerting\n threshold. The responsible team should follow this guide to decide\n whether this warrants further investigation.\n </Typography>\n\n <Box mt={4}>\n <Typography variant=\"h3\">Is the growth expected?</Typography>\n <Typography paragraph>\n The first question to ask is whether growth is expected. Perhaps a new\n product has been deployed, or additional regions added for\n reliability.\n </Typography>\n <Typography paragraph>\n Many services increase cost linearly with load. Has the demand\n increased? This may happen as you open new markets, or run marketing\n offers. Costs should be compared against a business metric, such as\n daily users, to normalize natural increases from business growth.\n </Typography>\n <Typography paragraph>\n Seasonal variance may also cause cost growth; yearly campaigns, an\n increase in demand during certain times of year.\n </Typography>\n <Typography paragraph>\n Cloud costs will often go up before they go down, in the case of\n migrations. Teams moving to new infrastructure may run in both the old\n and new environment during the migration.\n </Typography>\n </Box>\n\n <Box mt={4}>\n <Typography variant=\"h3\">Is the growth significant?</Typography>\n <Typography paragraph>\n Next, evaluate whether the growth is significant. This helps avoid\n premature optimization, where cost in engineering time is more than\n would be saved from the optimization over a reasonable time frame.\n </Typography>\n <Typography paragraph>\n We recommend reframing the cost growth itself in terms of engineering\n time. How much engineering time, for an <i>average</i> fully-loaded\n engineer cost at the company, is being overspent each month? Compare\n this to expected engineering time for optimization to decide whether\n the optimization is worthwhile.\n </Typography>\n </Box>\n\n <Box mt={4}>\n <Typography variant=\"h3\">\n Identifying which cloud product contributed most\n </Typography>\n <Typography paragraph>\n For projects meeting the alert threshold, Cost Insights shows a cost\n comparison of cloud products over the examined time period:\n </Typography>\n <Box mt={2} mb={2}>\n {projectGrowthAlert.element}\n </Box>\n <Typography paragraph>\n This allows you to quickly see which cloud products contributed to the\n growth in cloud costs.\n </Typography>\n </Box>\n\n <Box mt={4}>\n <Typography variant=\"h3\">\n Identifying the responsible workload\n </Typography>\n <Typography paragraph>\n After identifying the cloud product, use the corresponding product\n panel in Cost Insights to find a particular workload (or <i>entity</i>\n ) that has grown in cost:\n </Typography>\n <Box mt={2} mb={2}>\n <InfoCard title={product.name} subheader=\"3 entities, sorted by cost\">\n <ProductInsightsChart\n billingDate={today}\n duration={Duration.P3M}\n entity={entity}\n />\n </InfoCard>\n </Box>\n <Typography paragraph>\n From here, you can dig into commit history or deployment logs to find\n probable causes of an unexpected spike in cost.\n </Typography>\n </Box>\n\n <Box mt={4}>\n <Typography variant=\"h3\">Optimizing the workload</Typography>\n <Typography paragraph>\n Workload optimization varies between cloud products, but there are a\n few general optimization areas to consider:\n </Typography>\n <Typography variant=\"h5\">Retention</Typography>\n <Typography paragraph>\n Is the workload or storage necessary? Truly idle or unused resources\n can be cleaned up for immediate cost savings. For storage, how long do\n we need the data? Many cloud products support retention policies to\n automatically delete data after a certain time period.\n </Typography>\n <Typography variant=\"h5\">Efficiency</Typography>\n <Typography paragraph>\n Is the workload using cloud resources efficiently? For compute\n resources, do the utilization metrics look reasonable? Autoscaling\n infrastructure, such as Kubernetes, can run workloads more efficiently\n without compromising reliability.\n </Typography>\n <Typography variant=\"h5\">Lifecycle</Typography>\n <Typography paragraph>\n Is the workload using an optimal pricing model? Some cloud products\n offer better pricing for data that is accessed less frequently.\n </Typography>\n </Box>\n </AlertInstructionsLayout>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import MenuItem from '@material-ui/core/MenuItem';
|
|
3
|
+
import Select from '@material-ui/core/Select';
|
|
4
|
+
import { useSelectStyles } from '../../utils/styles.esm.js';
|
|
5
|
+
|
|
6
|
+
const ProjectSelect = ({
|
|
7
|
+
project,
|
|
8
|
+
projects,
|
|
9
|
+
onSelect
|
|
10
|
+
}) => {
|
|
11
|
+
const classes = useSelectStyles();
|
|
12
|
+
const projectOptions = projects.filter((p) => p.id).sort(
|
|
13
|
+
(a, b) => (a.name ?? a.id).localeCompare(b.name ?? b.id)
|
|
14
|
+
);
|
|
15
|
+
const handleOnChange = (e) => {
|
|
16
|
+
onSelect(e.target.value);
|
|
17
|
+
};
|
|
18
|
+
const renderValue = (value) => {
|
|
19
|
+
const proj = value;
|
|
20
|
+
const projectObj = projects.find((p) => p.id === proj);
|
|
21
|
+
return /* @__PURE__ */ React.createElement("b", { "data-testid": `selected-${proj}` }, proj === "all" ? "All Projects" : projectObj?.name ?? proj);
|
|
22
|
+
};
|
|
23
|
+
return /* @__PURE__ */ React.createElement(
|
|
24
|
+
Select,
|
|
25
|
+
{
|
|
26
|
+
className: classes.select,
|
|
27
|
+
variant: "outlined",
|
|
28
|
+
value: project ?? "all",
|
|
29
|
+
renderValue,
|
|
30
|
+
onChange: handleOnChange,
|
|
31
|
+
"data-testid": "project-filter-select"
|
|
32
|
+
},
|
|
33
|
+
[{ id: "all" }, ...projectOptions].map((proj) => /* @__PURE__ */ React.createElement(
|
|
34
|
+
MenuItem,
|
|
35
|
+
{
|
|
36
|
+
className: `${classes.menuItem} compact`,
|
|
37
|
+
key: proj.id,
|
|
38
|
+
value: proj.id,
|
|
39
|
+
"data-testid": `option-${proj.id}`
|
|
40
|
+
},
|
|
41
|
+
proj.id === "all" ? "All Projects" : proj.name ?? proj.id
|
|
42
|
+
))
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export { ProjectSelect };
|
|
47
|
+
//# sourceMappingURL=ProjectSelect.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectSelect.esm.js","sources":["../../../src/components/ProjectSelect/ProjectSelect.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 */\nimport React from 'react';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport Select from '@material-ui/core/Select';\nimport {\n Maybe,\n Project,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { useSelectStyles as useStyles } from '../../utils/styles';\n\ntype ProjectSelectProps = {\n project: Maybe<string>;\n projects: Array<Project>;\n onSelect: (project: Maybe<string>) => void;\n};\n\nexport const ProjectSelect = ({\n project,\n projects,\n onSelect,\n}: ProjectSelectProps) => {\n const classes = useStyles();\n\n const projectOptions = projects\n .filter(p => p.id)\n .sort((a, b) =>\n ((a.name ?? a.id) as string).localeCompare((b.name ?? b.id) as string),\n );\n\n const handleOnChange = (e: React.ChangeEvent<{ value: unknown }>) => {\n onSelect(e.target.value as string);\n };\n\n const renderValue = (value: unknown) => {\n const proj = value as string;\n const projectObj = projects.find(p => p.id === proj);\n return (\n <b data-testid={`selected-${proj}`}>\n {proj === 'all' ? 'All Projects' : projectObj?.name ?? proj}\n </b>\n );\n };\n\n return (\n <Select\n className={classes.select}\n variant=\"outlined\"\n value={project ?? 'all'}\n renderValue={renderValue}\n onChange={handleOnChange}\n data-testid=\"project-filter-select\"\n >\n {[{ id: 'all' }, ...projectOptions].map(proj => (\n <MenuItem\n className={`${classes.menuItem} compact`}\n key={proj.id}\n value={proj.id}\n data-testid={`option-${proj.id}`}\n >\n {proj.id === 'all' ? 'All Projects' : proj.name ?? proj.id}\n </MenuItem>\n ))}\n </Select>\n );\n};\n"],"names":["useStyles"],"mappings":";;;;;AA8BO,MAAM,gBAAgB,CAAC;AAAA,EAC5B,OAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AACF,CAA0B,KAAA;AACxB,EAAA,MAAM,UAAUA,eAAU,EAAA,CAAA;AAE1B,EAAA,MAAM,iBAAiB,QACpB,CAAA,MAAA,CAAO,CAAK,CAAA,KAAA,CAAA,CAAE,EAAE,CAChB,CAAA,IAAA;AAAA,IAAK,CAAC,CAAG,EAAA,CAAA,KAAA,CACN,CAAE,CAAA,IAAA,IAAQ,CAAE,CAAA,EAAA,EAAe,aAAe,CAAA,CAAA,CAAE,IAAQ,IAAA,CAAA,CAAE,EAAa,CAAA;AAAA,GACvE,CAAA;AAEF,EAAM,MAAA,cAAA,GAAiB,CAAC,CAA6C,KAAA;AACnE,IAAS,QAAA,CAAA,CAAA,CAAE,OAAO,KAAe,CAAA,CAAA;AAAA,GACnC,CAAA;AAEA,EAAM,MAAA,WAAA,GAAc,CAAC,KAAmB,KAAA;AACtC,IAAA,MAAM,IAAO,GAAA,KAAA,CAAA;AACb,IAAA,MAAM,aAAa,QAAS,CAAA,IAAA,CAAK,CAAK,CAAA,KAAA,CAAA,CAAE,OAAO,IAAI,CAAA,CAAA;AACnD,IACE,uBAAA,KAAA,CAAA,aAAA,CAAC,GAAE,EAAA,EAAA,aAAA,EAAa,CAAY,SAAA,EAAA,IAAI,CAC7B,CAAA,EAAA,EAAA,IAAA,KAAS,KAAQ,GAAA,cAAA,GAAiB,UAAY,EAAA,IAAA,IAAQ,IACzD,CAAA,CAAA;AAAA,GAEJ,CAAA;AAEA,EACE,uBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,WAAW,OAAQ,CAAA,MAAA;AAAA,MACnB,OAAQ,EAAA,UAAA;AAAA,MACR,OAAO,OAAW,IAAA,KAAA;AAAA,MAClB,WAAA;AAAA,MACA,QAAU,EAAA,cAAA;AAAA,MACV,aAAY,EAAA,uBAAA;AAAA,KAAA;AAAA,IAEX,CAAC,EAAE,EAAI,EAAA,KAAA,IAAS,GAAG,cAAc,CAAE,CAAA,GAAA,CAAI,CACtC,IAAA,qBAAA,KAAA,CAAA,aAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,CAAG,EAAA,OAAA,CAAQ,QAAQ,CAAA,QAAA,CAAA;AAAA,QAC9B,KAAK,IAAK,CAAA,EAAA;AAAA,QACV,OAAO,IAAK,CAAA,EAAA;AAAA,QACZ,aAAA,EAAa,CAAU,OAAA,EAAA,IAAA,CAAK,EAAE,CAAA,CAAA;AAAA,OAAA;AAAA,MAE7B,KAAK,EAAO,KAAA,KAAA,GAAQ,cAAiB,GAAA,IAAA,CAAK,QAAQ,IAAK,CAAA,EAAA;AAAA,KAE3D,CAAA;AAAA,GACH,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import pluralize from 'pluralize';
|
|
3
|
+
import Box from '@material-ui/core/Box';
|
|
4
|
+
import { BarChart } from '../BarChart/BarChart.esm.js';
|
|
5
|
+
import { BarChartLegend } from '../BarChart/BarChartLegend.esm.js';
|
|
6
|
+
import 'classnames';
|
|
7
|
+
import '@material-ui/core/Divider';
|
|
8
|
+
import '@material-ui/core/Typography';
|
|
9
|
+
import { useBarChartLayoutStyles } from '../../utils/styles.esm.js';
|
|
10
|
+
import '@material-ui/icons/Lens';
|
|
11
|
+
import { InfoCard } from '@backstage/core-components';
|
|
12
|
+
|
|
13
|
+
const UnlabeledDataflowAlertCard = ({
|
|
14
|
+
alert
|
|
15
|
+
}) => {
|
|
16
|
+
const classes = useBarChartLayoutStyles();
|
|
17
|
+
const projects = pluralize("project", alert.projects.length, true);
|
|
18
|
+
const subheader = `
|
|
19
|
+
Showing costs from ${projects} with unlabeled Dataflow jobs in the last 30 days.
|
|
20
|
+
`;
|
|
21
|
+
const options = {
|
|
22
|
+
previousName: "Unlabeled Cost",
|
|
23
|
+
currentName: "Labeled Cost"
|
|
24
|
+
};
|
|
25
|
+
const resources = alert.projects.map((project) => ({
|
|
26
|
+
name: project.id,
|
|
27
|
+
previous: project.unlabeledCost,
|
|
28
|
+
current: project.labeledCost
|
|
29
|
+
}));
|
|
30
|
+
return /* @__PURE__ */ React.createElement(InfoCard, { title: "Label Dataflow", subheader }, /* @__PURE__ */ React.createElement(Box, { className: classes.wrapper }, /* @__PURE__ */ React.createElement(
|
|
31
|
+
BarChartLegend,
|
|
32
|
+
{
|
|
33
|
+
costStart: alert.unlabeledCost,
|
|
34
|
+
costEnd: alert.labeledCost,
|
|
35
|
+
options
|
|
36
|
+
}
|
|
37
|
+
), /* @__PURE__ */ React.createElement(BarChart, { resources, options })));
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { UnlabeledDataflowAlertCard };
|
|
41
|
+
//# sourceMappingURL=UnlabeledDataflowAlertCard.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnlabeledDataflowAlertCard.esm.js","sources":["../../../src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.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 from 'react';\nimport pluralize from 'pluralize';\nimport Box from '@material-ui/core/Box';\nimport { BarChart, BarChartLegend } from '../BarChart';\nimport { UnlabeledDataflowData, ResourceData } from '../../types';\nimport { useBarChartLayoutStyles as useStyles } from '../../utils/styles';\nimport { InfoCard } from '@backstage/core-components';\n\ntype UnlabeledDataflowAlertProps = {\n alert: UnlabeledDataflowData;\n};\n\nexport const UnlabeledDataflowAlertCard = ({\n alert,\n}: UnlabeledDataflowAlertProps) => {\n const classes = useStyles();\n const projects = pluralize('project', alert.projects.length, true);\n const subheader = `\n Showing costs from ${projects} with unlabeled Dataflow jobs in the last 30 days.\n `;\n const options = {\n previousName: 'Unlabeled Cost',\n currentName: 'Labeled Cost',\n };\n\n const resources: ResourceData[] = alert.projects.map(project => ({\n name: project.id,\n previous: project.unlabeledCost,\n current: project.labeledCost,\n }));\n\n return (\n <InfoCard title=\"Label Dataflow\" subheader={subheader}>\n <Box className={classes.wrapper}>\n <BarChartLegend\n costStart={alert.unlabeledCost}\n costEnd={alert.labeledCost}\n options={options}\n />\n <BarChart resources={resources} options={options} />\n </Box>\n </InfoCard>\n );\n};\n"],"names":["useStyles"],"mappings":";;;;;;;;;;;;AA4BO,MAAM,6BAA6B,CAAC;AAAA,EACzC,KAAA;AACF,CAAmC,KAAA;AACjC,EAAA,MAAM,UAAUA,uBAAU,EAAA,CAAA;AAC1B,EAAA,MAAM,WAAW,SAAU,CAAA,SAAA,EAAW,KAAM,CAAA,QAAA,CAAS,QAAQ,IAAI,CAAA,CAAA;AACjE,EAAA,MAAM,SAAY,GAAA,CAAA;AAAA,uBAAA,EACK,QAAQ,CAAA;AAAA,EAAA,CAAA,CAAA;AAE/B,EAAA,MAAM,OAAU,GAAA;AAAA,IACd,YAAc,EAAA,gBAAA;AAAA,IACd,WAAa,EAAA,cAAA;AAAA,GACf,CAAA;AAEA,EAAA,MAAM,SAA4B,GAAA,KAAA,CAAM,QAAS,CAAA,GAAA,CAAI,CAAY,OAAA,MAAA;AAAA,IAC/D,MAAM,OAAQ,CAAA,EAAA;AAAA,IACd,UAAU,OAAQ,CAAA,aAAA;AAAA,IAClB,SAAS,OAAQ,CAAA,WAAA;AAAA,GACjB,CAAA,CAAA,CAAA;AAEF,EACE,uBAAA,KAAA,CAAA,aAAA,CAAC,YAAS,KAAM,EAAA,gBAAA,EAAiB,6BAC9B,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA,EAAI,SAAW,EAAA,OAAA,CAAQ,OACtB,EAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,cAAA;AAAA,IAAA;AAAA,MACC,WAAW,KAAM,CAAA,aAAA;AAAA,MACjB,SAAS,KAAM,CAAA,WAAA;AAAA,MACf,OAAA;AAAA,KAAA;AAAA,qBAED,KAAA,CAAA,aAAA,CAAA,QAAA,EAAA,EAAS,SAAsB,EAAA,OAAA,EAAkB,CACpD,CACF,CAAA,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Typography from '@material-ui/core/Typography';
|
|
3
|
+
import Box from '@material-ui/core/Box';
|
|
4
|
+
import Grid from '@material-ui/core/Grid';
|
|
5
|
+
import Container from '@material-ui/core/Container';
|
|
6
|
+
import Divider from '@material-ui/core/Divider';
|
|
7
|
+
|
|
8
|
+
const WhyCostsMatter = () => {
|
|
9
|
+
return /* @__PURE__ */ React.createElement(Box, { mt: 10, mb: 4 }, /* @__PURE__ */ React.createElement(Container, { maxWidth: "md" }, /* @__PURE__ */ React.createElement(Box, { mt: 2, mb: 2 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5", align: "center", gutterBottom: true }, "Why cloud costs matter")), /* @__PURE__ */ React.createElement(Grid, { container: true, alignContent: "space-around", spacing: 3, wrap: "nowrap" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, "Sustainability", " ", /* @__PURE__ */ React.createElement(Typography, { component: "span", role: "img", "aria-label": "globe" }, "\u{1F30E}")), /* @__PURE__ */ React.createElement(Typography, null, "Reducing cloud usage improves our carbon footprint.")), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Divider, { orientation: "vertical" })), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, "Revenue", " ", /* @__PURE__ */ React.createElement(
|
|
10
|
+
Typography,
|
|
11
|
+
{
|
|
12
|
+
component: "span",
|
|
13
|
+
role: "img",
|
|
14
|
+
"aria-label": "money-with-wings"
|
|
15
|
+
},
|
|
16
|
+
"\u{1F4B8}"
|
|
17
|
+
)), /* @__PURE__ */ React.createElement(Typography, null, "Keeping cloud costs well-tuned prevents infrastructure from eating into revenue.")), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Divider, { orientation: "vertical" })), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, "Innovation", " ", /* @__PURE__ */ React.createElement(Typography, { component: "span", role: "img", "aria-label": "medal" }, "\u{1F947}")), /* @__PURE__ */ React.createElement(Typography, null, "The more we save, the more we can reinvest in speed and innovation.")))));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { WhyCostsMatter };
|
|
21
|
+
//# sourceMappingURL=WhyCostsMatter.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WhyCostsMatter.esm.js","sources":["../../../src/components/WhyCostsMatter/WhyCostsMatter.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 from 'react';\nimport Typography from '@material-ui/core/Typography';\nimport Box from '@material-ui/core/Box';\nimport Grid from '@material-ui/core/Grid';\nimport Container from '@material-ui/core/Container';\nimport Divider from '@material-ui/core/Divider';\n\nexport const WhyCostsMatter = () => {\n return (\n <Box mt={10} mb={4}>\n <Container maxWidth=\"md\">\n <Box mt={2} mb={2}>\n <Typography variant=\"h5\" align=\"center\" gutterBottom>\n Why cloud costs matter\n </Typography>\n </Box>\n <Grid container alignContent=\"space-around\" spacing={3} wrap=\"nowrap\">\n <Grid item>\n <Typography variant=\"h6\">\n Sustainability{' '}\n <Typography component=\"span\" role=\"img\" aria-label=\"globe\">\n 🌎\n </Typography>\n </Typography>\n <Typography>\n Reducing cloud usage improves our carbon footprint.\n </Typography>\n </Grid>\n <Grid item>\n <Divider orientation=\"vertical\" />\n </Grid>\n <Grid item>\n <Typography variant=\"h6\">\n Revenue{' '}\n <Typography\n component=\"span\"\n role=\"img\"\n aria-label=\"money-with-wings\"\n >\n 💸\n </Typography>\n </Typography>\n <Typography>\n Keeping cloud costs well-tuned prevents infrastructure from eating\n into revenue.\n </Typography>\n </Grid>\n <Grid item>\n <Divider orientation=\"vertical\" />\n </Grid>\n <Grid item>\n <Typography variant=\"h6\">\n Innovation{' '}\n <Typography component=\"span\" role=\"img\" aria-label=\"medal\">\n 🥇\n </Typography>\n </Typography>\n <Typography>\n The more we save, the more we can reinvest in speed and\n innovation.\n </Typography>\n </Grid>\n </Grid>\n </Container>\n </Box>\n );\n};\n"],"names":[],"mappings":";;;;;;;AAuBO,MAAM,iBAAiB,MAAM;AAClC,EAAA,2CACG,GAAI,EAAA,EAAA,EAAA,EAAI,EAAI,EAAA,EAAA,EAAI,qBACd,KAAA,CAAA,aAAA,CAAA,SAAA,EAAA,EAAU,QAAS,EAAA,IAAA,EAAA,sCACjB,GAAI,EAAA,EAAA,EAAA,EAAI,GAAG,EAAI,EAAA,CAAA,EAAA,sCACb,UAAW,EAAA,EAAA,OAAA,EAAQ,IAAK,EAAA,KAAA,EAAM,UAAS,YAAY,EAAA,IAAA,EAAA,EAAC,wBAErD,CACF,CAAA,sCACC,IAAK,EAAA,EAAA,SAAA,EAAS,IAAC,EAAA,YAAA,EAAa,gBAAe,OAAS,EAAA,CAAA,EAAG,MAAK,QAC3D,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,QAAK,IAAI,EAAA,IAAA,EAAA,kBACP,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,SAAQ,IAAK,EAAA,EAAA,gBAAA,EACR,GACf,kBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,SAAU,EAAA,MAAA,EAAO,IAAK,EAAA,KAAA,EAAM,cAAW,OAAQ,EAAA,EAAA,WAE3D,CACF,CACA,kBAAA,KAAA,CAAA,aAAA,CAAC,kBAAW,qDAEZ,CACF,CACA,kBAAA,KAAA,CAAA,aAAA,CAAC,QAAK,IAAI,EAAA,IAAA,EAAA,sCACP,OAAQ,EAAA,EAAA,WAAA,EAAY,YAAW,CAClC,CAAA,kBACC,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,MAAI,IACR,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,OAAQ,EAAA,IAAA,EAAA,EAAK,WACf,GACR,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,SAAU,EAAA,MAAA;AAAA,MACV,IAAK,EAAA,KAAA;AAAA,MACL,YAAW,EAAA,kBAAA;AAAA,KAAA;AAAA,IACZ,WAAA;AAAA,GAGH,mBACC,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,IAAA,EAAW,kFAGZ,CACF,CAAA,sCACC,IAAK,EAAA,EAAA,IAAA,EAAI,wBACP,KAAA,CAAA,aAAA,CAAA,OAAA,EAAA,EAAQ,aAAY,UAAW,EAAA,CAClC,mBACC,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,MAAI,IACR,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,OAAQ,EAAA,IAAA,EAAA,EAAK,cACZ,GACX,kBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,SAAU,EAAA,MAAA,EAAO,MAAK,KAAM,EAAA,YAAA,EAAW,WAAQ,WAE3D,CACF,mBACC,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,IAAA,EAAW,qEAGZ,CACF,CACF,CACF,CACF,CAAA,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import pluralize from 'pluralize';
|
|
3
|
+
import Typography from '@material-ui/core/Typography';
|
|
4
|
+
import { KubernetesMigrationAlertCard } from '../components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.esm.js';
|
|
5
|
+
import { AlertStatus } from '../../types/Alert.esm.js';
|
|
6
|
+
import { KubernetesMigrationDismissForm } from '../forms/KubernetesMigrationDismissForm.esm.js';
|
|
7
|
+
import { Lifecycle } from '@backstage/core-components';
|
|
8
|
+
|
|
9
|
+
class KubernetesMigrationAlert {
|
|
10
|
+
api;
|
|
11
|
+
data;
|
|
12
|
+
subtitle = "Services running on Kubernetes are estimated to save 50% or more compared to Compute Engine.";
|
|
13
|
+
// Dialog will not render a form if form property set to null.
|
|
14
|
+
AcceptForm = null;
|
|
15
|
+
// Overrides default Dismiss form with a custom form component.
|
|
16
|
+
DismissForm = KubernetesMigrationDismissForm;
|
|
17
|
+
constructor(api, data) {
|
|
18
|
+
this.api = api;
|
|
19
|
+
this.data = data;
|
|
20
|
+
}
|
|
21
|
+
get title() {
|
|
22
|
+
return /* @__PURE__ */ React.createElement(Typography, { component: "span" }, "Consider migrating", " ", pluralize("service", this.data.services.length, true), " to Kubernetes", " ", /* @__PURE__ */ React.createElement(Lifecycle, { shorthand: true }));
|
|
23
|
+
}
|
|
24
|
+
get element() {
|
|
25
|
+
const subheader = `${pluralize(
|
|
26
|
+
"Service",
|
|
27
|
+
this.data.services.length,
|
|
28
|
+
true
|
|
29
|
+
)}, sorted by cost`;
|
|
30
|
+
return /* @__PURE__ */ React.createElement(
|
|
31
|
+
KubernetesMigrationAlertCard,
|
|
32
|
+
{
|
|
33
|
+
data: this.data,
|
|
34
|
+
title: "Migrate to Kubernetes",
|
|
35
|
+
subheader,
|
|
36
|
+
currentProduct: "Compute Engine",
|
|
37
|
+
comparedProduct: "Kubernetes"
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
/* Fires when the onSubmit event is raised on a Dismiss form. Displays custom dismiss form. */
|
|
42
|
+
async onDismissed(options) {
|
|
43
|
+
const alerts = await this.api.getAlerts(options.group);
|
|
44
|
+
return new Promise(
|
|
45
|
+
(resolve) => setTimeout(resolve, 750, [
|
|
46
|
+
...alerts.filter((a) => a.title !== this.title),
|
|
47
|
+
{
|
|
48
|
+
title: this.title,
|
|
49
|
+
subtitle: this.subtitle,
|
|
50
|
+
status: AlertStatus.Dismissed
|
|
51
|
+
}
|
|
52
|
+
])
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
/* Fires when the onSubmit event is raised on a Snooze form. Displays default snooze form. */
|
|
56
|
+
async onSnoozed(options) {
|
|
57
|
+
const alerts = await this.api.getAlerts(options.group);
|
|
58
|
+
return new Promise(
|
|
59
|
+
(resolve) => setTimeout(resolve, 750, [
|
|
60
|
+
...alerts.filter((a) => a.title !== this.title),
|
|
61
|
+
{
|
|
62
|
+
title: this.title,
|
|
63
|
+
subtitle: this.subtitle,
|
|
64
|
+
status: AlertStatus.Snoozed
|
|
65
|
+
}
|
|
66
|
+
])
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
/* Fires when the Accept button is clicked. Dialog does not render a form. See KubernetesMigrationAlert.AcceptForm */
|
|
70
|
+
async onAccepted(options) {
|
|
71
|
+
const alerts = await this.api.getAlerts(options.group);
|
|
72
|
+
return new Promise(
|
|
73
|
+
(resolve) => setTimeout(resolve, 750, [
|
|
74
|
+
...alerts.filter((a) => a.title !== this.title),
|
|
75
|
+
{
|
|
76
|
+
title: this.title,
|
|
77
|
+
subtitle: this.subtitle,
|
|
78
|
+
status: AlertStatus.Accepted
|
|
79
|
+
}
|
|
80
|
+
])
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { KubernetesMigrationAlert };
|
|
86
|
+
//# sourceMappingURL=KubernetesMigrationAlert.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KubernetesMigrationAlert.esm.js","sources":["../../../src/example/alerts/KubernetesMigrationAlert.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 from 'react';\nimport pluralize from 'pluralize';\nimport Typography from '@material-ui/core/Typography';\nimport { KubernetesMigrationAlertCard } from '../components';\nimport { CostInsightsApi } from '../../api';\nimport {\n Alert,\n AlertForm,\n AlertOptions,\n AlertStatus,\n AlertSnoozeFormData,\n} from '../../types';\nimport {\n ChangeStatistic,\n Entity,\n} from '@backstage-community/plugin-cost-insights-common';\nimport {\n KubernetesMigrationDismissForm,\n KubernetesMigrationDismissFormData,\n} from '../forms';\nimport { Lifecycle } from '@backstage/core-components';\n\nexport interface KubernetesMigrationData {\n startDate: string;\n endDate: string;\n change: ChangeStatistic;\n services: Array<Entity>;\n}\n\nexport interface KubernetesMigrationApi extends Alert {\n api: CostInsightsApi;\n data: KubernetesMigrationData;\n}\n\n/**\n * This is an example of an Alert implementation using optional event hooks.\n *\n * Event hooks can be used to enable users to dismiss, snooze, or accept an action item\n * - or any combination thereof. Defining a hook will generate default UI - button, dialog and\n * form. Cost Insights does not preserve client side alert state - each hook is expected to return a new set of alerts.\n *\n * Snoozed, accepted, etc. alerts should define a corresponding status property. Alerts will be aggregated\n * by status in a collapsed view below Alert Insights section and a badge will appear in Action Items\n * showing the total alerts of that status.\n *\n * Customizing Alerts\n * Default forms can be overridden in two ways - by setting a form property to null or defining a custom component.\n *\n * If a form property is set to null, the Dialog will not render a form. This can be useful in scenarios\n * where data isn't needed from the user such as when a user accepts an action item's recommendation.\n *\n * If a form property is set to a React component, the Dialog will render the form component in place of the default form.\n * Form components must return valid form elements, and accept a ref and onSubmit event handler.\n * Custom forms must implement the corresponding event hook. See /forms for example implementations.\n */\n\nexport class KubernetesMigrationAlert implements KubernetesMigrationApi {\n api: CostInsightsApi;\n data: KubernetesMigrationData;\n\n subtitle =\n 'Services running on Kubernetes are estimated to save 50% or more compared to Compute Engine.';\n\n // Dialog will not render a form if form property set to null.\n AcceptForm = null;\n // Overrides default Dismiss form with a custom form component.\n DismissForm: AlertForm<\n KubernetesMigrationAlert,\n KubernetesMigrationDismissFormData\n > = KubernetesMigrationDismissForm;\n\n constructor(api: CostInsightsApi, data: KubernetesMigrationData) {\n this.api = api;\n this.data = data;\n }\n\n get title() {\n return (\n <Typography component=\"span\">\n Consider migrating{' '}\n {pluralize('service', this.data.services.length, true)} to Kubernetes{' '}\n <Lifecycle shorthand />\n </Typography>\n );\n }\n\n get element() {\n const subheader = `${pluralize(\n 'Service',\n this.data.services.length,\n true,\n )}, sorted by cost`;\n return (\n <KubernetesMigrationAlertCard\n data={this.data}\n title=\"Migrate to Kubernetes\"\n subheader={subheader}\n currentProduct=\"Compute Engine\"\n comparedProduct=\"Kubernetes\"\n />\n );\n }\n\n /* Fires when the onSubmit event is raised on a Dismiss form. Displays custom dismiss form. */\n async onDismissed(\n options: AlertOptions<KubernetesMigrationDismissFormData>,\n ): Promise<Alert[]> {\n const alerts = await this.api.getAlerts(options.group);\n return new Promise(resolve =>\n setTimeout(resolve, 750, [\n ...alerts.filter(a => a.title !== this.title),\n {\n title: this.title,\n subtitle: this.subtitle,\n status: AlertStatus.Dismissed,\n },\n ]),\n );\n }\n\n /* Fires when the onSubmit event is raised on a Snooze form. Displays default snooze form. */\n async onSnoozed(\n options: AlertOptions<AlertSnoozeFormData>,\n ): Promise<Alert[]> {\n const alerts = await this.api.getAlerts(options.group);\n return new Promise(resolve =>\n setTimeout(resolve, 750, [\n ...alerts.filter(a => a.title !== this.title),\n {\n title: this.title,\n subtitle: this.subtitle,\n status: AlertStatus.Snoozed,\n },\n ]),\n );\n }\n\n /* Fires when the Accept button is clicked. Dialog does not render a form. See KubernetesMigrationAlert.AcceptForm */\n async onAccepted(options: AlertOptions<null>): Promise<Alert[]> {\n const alerts = await this.api.getAlerts(options.group);\n return new Promise(resolve =>\n setTimeout(resolve, 750, [\n ...alerts.filter(a => a.title !== this.title),\n {\n title: this.title,\n subtitle: this.subtitle,\n status: AlertStatus.Accepted,\n },\n ]),\n );\n }\n}\n"],"names":[],"mappings":"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"}
|