@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.
Files changed (231) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +6 -6
  3. package/dist/alerts/ProjectGrowthAlert.esm.js +24 -0
  4. package/dist/alerts/ProjectGrowthAlert.esm.js.map +1 -0
  5. package/dist/alerts/UnlabeledDataflowAlert.esm.js +25 -0
  6. package/dist/alerts/UnlabeledDataflowAlert.esm.js.map +1 -0
  7. package/dist/api/CostInsightsApi.esm.js +8 -0
  8. package/dist/api/CostInsightsApi.esm.js.map +1 -0
  9. package/dist/components/ActionItems/ActionItemCard.esm.js +46 -0
  10. package/dist/components/ActionItems/ActionItemCard.esm.js.map +1 -0
  11. package/dist/components/ActionItems/ActionItems.esm.js +90 -0
  12. package/dist/components/ActionItems/ActionItems.esm.js.map +1 -0
  13. package/dist/components/AlertInsights/AlertDialog.esm.js +123 -0
  14. package/dist/components/AlertInsights/AlertDialog.esm.js.map +1 -0
  15. package/dist/components/AlertInsights/AlertInsights.esm.js +157 -0
  16. package/dist/components/AlertInsights/AlertInsights.esm.js.map +1 -0
  17. package/dist/components/AlertInsights/AlertInsightsHeader.esm.js +17 -0
  18. package/dist/components/AlertInsights/AlertInsightsHeader.esm.js.map +1 -0
  19. package/dist/components/AlertInsights/AlertInsightsSection.esm.js +57 -0
  20. package/dist/components/AlertInsights/AlertInsightsSection.esm.js.map +1 -0
  21. package/dist/components/AlertInsights/AlertInsightsSectionHeader.esm.js +30 -0
  22. package/dist/components/AlertInsights/AlertInsightsSectionHeader.esm.js.map +1 -0
  23. package/dist/components/AlertInsights/AlertStatusSummary.esm.js +93 -0
  24. package/dist/components/AlertInsights/AlertStatusSummary.esm.js.map +1 -0
  25. package/dist/components/AlertInsights/AlertStatusSummaryButton.esm.js +35 -0
  26. package/dist/components/AlertInsights/AlertStatusSummaryButton.esm.js.map +1 -0
  27. package/dist/components/AlertInstructionsLayout/AlertInstructionsLayout.esm.js +47 -0
  28. package/dist/components/AlertInstructionsLayout/AlertInstructionsLayout.esm.js.map +1 -0
  29. package/dist/components/BarChart/BarChart.esm.js +167 -0
  30. package/dist/components/BarChart/BarChart.esm.js.map +1 -0
  31. package/dist/components/BarChart/BarChartLabel.esm.js +31 -0
  32. package/dist/components/BarChart/BarChartLabel.esm.js.map +1 -0
  33. package/dist/components/BarChart/BarChartLegend.esm.js +47 -0
  34. package/dist/components/BarChart/BarChartLegend.esm.js.map +1 -0
  35. package/dist/components/BarChart/BarChartStepper.esm.js +91 -0
  36. package/dist/components/BarChart/BarChartStepper.esm.js.map +1 -0
  37. package/dist/components/BarChart/BarChartStepperButton.esm.js +27 -0
  38. package/dist/components/BarChart/BarChartStepperButton.esm.js.map +1 -0
  39. package/dist/components/BarChart/BarChartSteps.esm.js +25 -0
  40. package/dist/components/BarChart/BarChartSteps.esm.js.map +1 -0
  41. package/dist/components/BarChart/BarChartTick.esm.js +29 -0
  42. package/dist/components/BarChart/BarChartTick.esm.js.map +1 -0
  43. package/dist/components/BarChart/BarChartTooltip.esm.js +30 -0
  44. package/dist/components/BarChart/BarChartTooltip.esm.js.map +1 -0
  45. package/dist/components/BarChart/BarChartTooltipItem.esm.js +24 -0
  46. package/dist/components/BarChart/BarChartTooltipItem.esm.js.map +1 -0
  47. package/dist/components/CopyUrlToClipboard/CopyUrlToClipboard.esm.js +44 -0
  48. package/dist/components/CopyUrlToClipboard/CopyUrlToClipboard.esm.js.map +1 -0
  49. package/dist/components/CostGrowth/CostGrowth.esm.js +55 -0
  50. package/dist/components/CostGrowth/CostGrowth.esm.js.map +1 -0
  51. package/dist/components/CostGrowth/CostGrowthIndicator.esm.js +31 -0
  52. package/dist/components/CostGrowth/CostGrowthIndicator.esm.js.map +1 -0
  53. package/dist/components/CostInsightsHeader/CostInsightsHeader.esm.js +57 -0
  54. package/dist/components/CostInsightsHeader/CostInsightsHeader.esm.js.map +1 -0
  55. package/dist/components/CostInsightsLayout/CostInsightsLayout.esm.js +35 -0
  56. package/dist/components/CostInsightsLayout/CostInsightsLayout.esm.js.map +1 -0
  57. package/dist/components/CostInsightsNavigation/CostInsightsNavigation.esm.js +87 -0
  58. package/dist/components/CostInsightsNavigation/CostInsightsNavigation.esm.js.map +1 -0
  59. package/dist/components/CostInsightsPage/CostInsightsPage.esm.js +229 -0
  60. package/dist/components/CostInsightsPage/CostInsightsPage.esm.js.map +1 -0
  61. package/dist/components/CostInsightsPage/CostInsightsPageRoot.esm.js +15 -0
  62. package/dist/components/CostInsightsPage/CostInsightsPageRoot.esm.js.map +1 -0
  63. package/dist/components/CostInsightsPage/CostInsightsThemeProvider.esm.js +24 -0
  64. package/dist/components/CostInsightsPage/CostInsightsThemeProvider.esm.js.map +1 -0
  65. package/dist/components/CostInsightsPage/index.esm.js +2 -0
  66. package/dist/components/CostInsightsPage/index.esm.js.map +1 -0
  67. package/dist/components/CostInsightsPage/selector.esm.js +15 -0
  68. package/dist/components/CostInsightsPage/selector.esm.js.map +1 -0
  69. package/dist/components/CostInsightsTabs/CostInsightsTabs.esm.js +89 -0
  70. package/dist/components/CostInsightsTabs/CostInsightsTabs.esm.js.map +1 -0
  71. package/dist/components/CostInsightsTabs/selector.esm.js +17 -0
  72. package/dist/components/CostInsightsTabs/selector.esm.js.map +1 -0
  73. package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js +196 -0
  74. package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js.map +1 -0
  75. package/dist/components/CostOverviewCard/CostOverviewCard.esm.js +99 -0
  76. package/dist/components/CostOverviewCard/CostOverviewCard.esm.js.map +1 -0
  77. package/dist/components/CostOverviewCard/CostOverviewChart.esm.js +157 -0
  78. package/dist/components/CostOverviewCard/CostOverviewChart.esm.js.map +1 -0
  79. package/dist/components/CostOverviewCard/CostOverviewHeader.esm.js +25 -0
  80. package/dist/components/CostOverviewCard/CostOverviewHeader.esm.js.map +1 -0
  81. package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js +54 -0
  82. package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js.map +1 -0
  83. package/dist/components/CostOverviewCard/selector.esm.js +22 -0
  84. package/dist/components/CostOverviewCard/selector.esm.js.map +1 -0
  85. package/dist/components/CurrencySelect/CurrencySelect.esm.js +52 -0
  86. package/dist/components/CurrencySelect/CurrencySelect.esm.js.map +1 -0
  87. package/dist/{esm/index-BPzWHt3A.esm.js → components/EntityCosts/EntityCosts.esm.js} +14 -80
  88. package/dist/{esm/index-BPzWHt3A.esm.js.map → components/EntityCosts/EntityCosts.esm.js.map} +1 -1
  89. package/dist/components/EntityCosts/index.esm.js +2 -0
  90. package/dist/components/EntityCosts/index.esm.js.map +1 -0
  91. package/dist/{esm/index-BA6m_qFS.esm.js → components/LabelDataflowInstructionsPage/LabelDataflowInstructionsPage.esm.js} +2 -79
  92. package/dist/components/LabelDataflowInstructionsPage/LabelDataflowInstructionsPage.esm.js.map +1 -0
  93. package/dist/components/LabelDataflowInstructionsPage/index.esm.js +2 -0
  94. package/dist/components/LabelDataflowInstructionsPage/index.esm.js.map +1 -0
  95. package/dist/components/LegendItem/LegendItem.esm.js +44 -0
  96. package/dist/components/LegendItem/LegendItem.esm.js.map +1 -0
  97. package/dist/components/MetricSelect/MetricSelect.esm.js +37 -0
  98. package/dist/components/MetricSelect/MetricSelect.esm.js.map +1 -0
  99. package/dist/components/PeriodSelect/PeriodSelect.esm.js +72 -0
  100. package/dist/components/PeriodSelect/PeriodSelect.esm.js.map +1 -0
  101. package/dist/components/ProductInsights/ProductInsights.esm.js +98 -0
  102. package/dist/components/ProductInsights/ProductInsights.esm.js.map +1 -0
  103. package/dist/components/ProductInsightsCard/ProductEntityDialog.esm.js +42 -0
  104. package/dist/components/ProductInsightsCard/ProductEntityDialog.esm.js.map +1 -0
  105. package/dist/components/ProductInsightsCard/ProductEntityTable.esm.js +139 -0
  106. package/dist/components/ProductInsightsCard/ProductEntityTable.esm.js.map +1 -0
  107. package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js +83 -0
  108. package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js.map +1 -0
  109. package/dist/components/ProductInsightsCard/ProductInsightsCardList.esm.js +34 -0
  110. package/dist/components/ProductInsightsCard/ProductInsightsCardList.esm.js.map +1 -0
  111. package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js +148 -0
  112. package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js.map +1 -0
  113. package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.esm.js +20 -0
  114. package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.esm.js.map +1 -0
  115. package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.esm.js +46 -0
  116. package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.esm.js.map +1 -0
  117. package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js +111 -0
  118. package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js.map +1 -0
  119. package/dist/components/ProjectGrowthInstructionsPage/index.esm.js +2 -0
  120. package/dist/components/ProjectGrowthInstructionsPage/index.esm.js.map +1 -0
  121. package/dist/components/ProjectSelect/ProjectSelect.esm.js +47 -0
  122. package/dist/components/ProjectSelect/ProjectSelect.esm.js.map +1 -0
  123. package/dist/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.esm.js +41 -0
  124. package/dist/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.esm.js.map +1 -0
  125. package/dist/components/WhyCostsMatter/WhyCostsMatter.esm.js +21 -0
  126. package/dist/components/WhyCostsMatter/WhyCostsMatter.esm.js.map +1 -0
  127. package/dist/example/alerts/KubernetesMigrationAlert.esm.js +86 -0
  128. package/dist/example/alerts/KubernetesMigrationAlert.esm.js.map +1 -0
  129. package/dist/example/client.esm.js +194 -0
  130. package/dist/example/client.esm.js.map +1 -0
  131. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.esm.js +33 -0
  132. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.esm.js.map +1 -0
  133. package/dist/{esm/index-BTIHr4n_.esm.js → example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js} +69 -48
  134. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js.map +1 -0
  135. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js +107 -0
  136. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js.map +1 -0
  137. package/dist/example/forms/KubernetesMigrationDismissForm.esm.js +57 -0
  138. package/dist/example/forms/KubernetesMigrationDismissForm.esm.js.map +1 -0
  139. package/dist/forms/AlertAcceptForm.esm.js +27 -0
  140. package/dist/forms/AlertAcceptForm.esm.js.map +1 -0
  141. package/dist/forms/AlertDismissForm.esm.js +100 -0
  142. package/dist/forms/AlertDismissForm.esm.js.map +1 -0
  143. package/dist/forms/AlertSnoozeForm.esm.js +52 -0
  144. package/dist/forms/AlertSnoozeForm.esm.js.map +1 -0
  145. package/dist/hooks/useConfig.esm.js +153 -0
  146. package/dist/hooks/useConfig.esm.js.map +1 -0
  147. package/dist/hooks/useCurrency.esm.js +26 -0
  148. package/dist/hooks/useCurrency.esm.js.map +1 -0
  149. package/dist/hooks/useFilters.esm.js +67 -0
  150. package/dist/hooks/useFilters.esm.js.map +1 -0
  151. package/dist/hooks/useGroups.esm.js +55 -0
  152. package/dist/hooks/useGroups.esm.js.map +1 -0
  153. package/dist/hooks/useLastCompleteBillingDate.esm.js +65 -0
  154. package/dist/hooks/useLastCompleteBillingDate.esm.js.map +1 -0
  155. package/dist/hooks/useLoading.esm.js +54 -0
  156. package/dist/hooks/useLoading.esm.js.map +1 -0
  157. package/dist/hooks/useScroll.esm.js +24 -0
  158. package/dist/hooks/useScroll.esm.js.map +1 -0
  159. package/dist/index.esm.js +37 -22
  160. package/dist/index.esm.js.map +1 -1
  161. package/dist/plugin.esm.js +55 -0
  162. package/dist/plugin.esm.js.map +1 -0
  163. package/dist/testUtils/filters.esm.js +8 -0
  164. package/dist/testUtils/filters.esm.js.map +1 -0
  165. package/dist/testUtils/loading.esm.js +14 -0
  166. package/dist/testUtils/loading.esm.js.map +1 -0
  167. package/dist/testUtils/mockData.esm.js +485 -0
  168. package/dist/testUtils/mockData.esm.js.map +1 -0
  169. package/dist/testUtils/products.esm.js +49 -0
  170. package/dist/testUtils/products.esm.js.map +1 -0
  171. package/dist/testUtils/providers.esm.js +41 -0
  172. package/dist/testUtils/providers.esm.js.map +1 -0
  173. package/dist/testUtils/testUtils.esm.js +142 -0
  174. package/dist/testUtils/testUtils.esm.js.map +1 -0
  175. package/dist/types/Alert.esm.js +66 -0
  176. package/dist/types/Alert.esm.js.map +1 -0
  177. package/dist/types/ChangeStatistic.esm.js +15 -0
  178. package/dist/types/ChangeStatistic.esm.js.map +1 -0
  179. package/dist/types/CurrencyType.esm.js +10 -0
  180. package/dist/types/CurrencyType.esm.js.map +1 -0
  181. package/dist/types/DateFormat.esm.js +4 -0
  182. package/dist/types/DateFormat.esm.js.map +1 -0
  183. package/dist/types/Duration.esm.js +10 -0
  184. package/dist/types/Duration.esm.js.map +1 -0
  185. package/dist/types/Icon.esm.js +12 -0
  186. package/dist/types/Icon.esm.js.map +1 -0
  187. package/dist/utils/alerts.esm.js +72 -0
  188. package/dist/utils/alerts.esm.js.map +1 -0
  189. package/dist/utils/assert.esm.js +29 -0
  190. package/dist/utils/assert.esm.js.map +1 -0
  191. package/dist/utils/change.esm.js +58 -0
  192. package/dist/utils/change.esm.js.map +1 -0
  193. package/dist/utils/charts.esm.js +9 -0
  194. package/dist/utils/charts.esm.js.map +1 -0
  195. package/dist/utils/config.esm.js +19 -0
  196. package/dist/utils/config.esm.js.map +1 -0
  197. package/dist/utils/currency.esm.js +56 -0
  198. package/dist/utils/currency.esm.js.map +1 -0
  199. package/dist/utils/duration.esm.js +55 -0
  200. package/dist/utils/duration.esm.js.map +1 -0
  201. package/dist/utils/filters.esm.js +14 -0
  202. package/dist/utils/filters.esm.js.map +1 -0
  203. package/dist/utils/formatters.esm.js +103 -0
  204. package/dist/utils/formatters.esm.js.map +1 -0
  205. package/dist/utils/grammar.esm.js +14 -0
  206. package/dist/utils/grammar.esm.js.map +1 -0
  207. package/dist/utils/graphs.esm.js +48 -0
  208. package/dist/utils/graphs.esm.js.map +1 -0
  209. package/dist/utils/history.esm.js +32 -0
  210. package/dist/utils/history.esm.js.map +1 -0
  211. package/dist/utils/loading.esm.js +51 -0
  212. package/dist/utils/loading.esm.js.map +1 -0
  213. package/dist/utils/navigation.esm.js +56 -0
  214. package/dist/utils/navigation.esm.js.map +1 -0
  215. package/dist/utils/scroll.esm.js +38 -0
  216. package/dist/utils/scroll.esm.js.map +1 -0
  217. package/dist/utils/sort.esm.js +10 -0
  218. package/dist/utils/sort.esm.js.map +1 -0
  219. package/dist/utils/styles.esm.js +562 -0
  220. package/dist/utils/styles.esm.js.map +1 -0
  221. package/dist/utils/sum.esm.js +4 -0
  222. package/dist/utils/sum.esm.js.map +1 -0
  223. package/package.json +17 -12
  224. package/dist/esm/index-BA6m_qFS.esm.js.map +0 -1
  225. package/dist/esm/index-BFM2mP6Z.esm.js +0 -4084
  226. package/dist/esm/index-BFM2mP6Z.esm.js.map +0 -1
  227. package/dist/esm/index-BTIHr4n_.esm.js.map +0 -1
  228. package/dist/esm/index-DaitkcPN.esm.js +0 -1136
  229. package/dist/esm/index-DaitkcPN.esm.js.map +0 -1
  230. package/dist/esm/selector-D7iXJwd9.esm.js +0 -594
  231. package/dist/esm/selector-D7iXJwd9.esm.js.map +0 -1
@@ -1,4084 +0,0 @@
1
- import { createRouteRef, createPlugin, createRoutableExtension, useApi, configApiRef, createApiRef, identityApiRef } from '@backstage/core-plugin-api';
2
- import React, { createContext, useState, useEffect, useContext, useMemo, useReducer, forwardRef, useCallback } from 'react';
3
- import pluralize from 'pluralize';
4
- import Typography from '@material-ui/core/Typography';
5
- import Box from '@material-ui/core/Box';
6
- import { makeStyles, createStyles, ThemeProvider, createTheme, useTheme } from '@material-ui/core/styles';
7
- import '@material-ui/core/Avatar';
8
- import Tooltip$1 from '@material-ui/core/Tooltip';
9
- import IconButton from '@material-ui/core/IconButton';
10
- import Badge from '@material-ui/core/Badge';
11
- import Paper from '@material-ui/core/Paper';
12
- import Divider from '@material-ui/core/Divider';
13
- import '@material-ui/icons/AccessTime';
14
- import '@material-ui/icons/Check';
15
- import '@material-ui/icons/Delete';
16
- import classnames from 'classnames';
17
- import '@material-ui/core/Card';
18
- import '@material-ui/core/CardHeader';
19
- import MoneyIcon from '@material-ui/icons/MonetizationOn';
20
- import ActionIcon from '@material-ui/icons/Whatshot';
21
- import Settings from '@material-ui/icons/Settings';
22
- import AccountTree from '@material-ui/icons/AccountTree';
23
- import Storage from '@material-ui/icons/Storage';
24
- import Search from '@material-ui/icons/Search';
25
- import CloudQueue from '@material-ui/icons/CloudQueue';
26
- import School from '@material-ui/icons/School';
27
- import ViewHeadline from '@material-ui/icons/ViewHeadline';
28
- import Alert from '@material-ui/lab/Alert';
29
- import { useNavigate, useLocation } from 'react-router-dom';
30
- import qs from 'qs';
31
- import * as yup from 'yup';
32
- import Backdrop from '@material-ui/core/Backdrop';
33
- import CircularProgress from '@material-ui/core/CircularProgress';
34
- import { DateTime, Duration as Duration$1 } from 'luxon';
35
- import { lighten, emphasize, getLuminance, darken } from '@material-ui/core/styles/colorManipulator';
36
- import { parseEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
37
- import '@material-ui/core/Grid';
38
- import '@material-ui/core/Snackbar';
39
- import '@material-ui/core/utils';
40
- import Button from '@material-ui/core/Button';
41
- import Dialog from '@material-ui/core/Dialog';
42
- import '@material-ui/core/DialogActions';
43
- import '@material-ui/core/DialogContent';
44
- import CloseIcon from '@material-ui/icons/Close';
45
- import Checkbox from '@material-ui/core/Checkbox';
46
- import FormControl from '@material-ui/core/FormControl';
47
- import FormControlLabel from '@material-ui/core/FormControlLabel';
48
- import RadioGroup from '@material-ui/core/RadioGroup';
49
- import Radio from '@material-ui/core/Radio';
50
- import Collapse from '@material-ui/core/Collapse';
51
- import TextField from '@material-ui/core/TextField';
52
- import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
53
- import Container from '@material-ui/core/Container';
54
- import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
55
- import { Page, Header, Table, HeaderTabs, InfoCard, Lifecycle } from '@backstage/core-components';
56
- import { ResponsiveContainer, BarChart as BarChart$1, Tooltip, CartesianGrid, XAxis, YAxis, Bar } from 'recharts';
57
- import Slide from '@material-ui/core/Slide';
58
- import ChevronRightIcon from '@material-ui/icons/ChevronRight';
59
- import ButtonBase from '@material-ui/core/ButtonBase';
60
- import LensIcon from '@material-ui/icons/Lens';
61
- import HelpOutlineOutlinedIcon from '@material-ui/icons/HelpOutlineOutlined';
62
- import 'react-use/esm/useCopyToClipboard';
63
- import '@material-ui/icons/AssignmentOutlined';
64
- import '@material-ui/icons/AssignmentTurnedInOutlined';
65
- import '@material-ui/icons/SentimentVeryDissatisfied';
66
- import ArrowDropUp from '@material-ui/icons/ArrowDropUp';
67
- import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
68
- import 'react-use/esm/useAsync';
69
- import Menu from '@material-ui/core/Menu';
70
- import MenuItem from '@material-ui/core/MenuItem';
71
- import Tab from '@material-ui/core/Tab';
72
- import Tabs from '@material-ui/core/Tabs';
73
- import MenuList from '@material-ui/core/MenuList';
74
- import ListItemIcon from '@material-ui/core/ListItemIcon';
75
- import ListItemText from '@material-ui/core/ListItemText';
76
- import '@material-ui/core/InputLabel';
77
- import '@material-ui/core/Select';
78
- import '@material-ui/core/CardContent';
79
- import FullScreenIcon from '@material-ui/icons/Fullscreen';
80
- import FormGroup from '@material-ui/core/FormGroup';
81
- import regression from 'regression';
82
-
83
- const rootRouteRef = createRouteRef({
84
- id: "cost-insights"
85
- });
86
- const projectGrowthAlertRef = createRouteRef({
87
- id: "cost-insights:investigating-growth"
88
- });
89
- const unlabeledDataflowAlertRef = createRouteRef({
90
- id: "cost-insights:labeling-jobs"
91
- });
92
- const costInsightsPlugin = createPlugin({
93
- id: "cost-insights",
94
- featureFlags: [{ name: "cost-insights-currencies" }],
95
- routes: {
96
- root: rootRouteRef,
97
- growthAlerts: projectGrowthAlertRef,
98
- unlabeledDataflowAlerts: unlabeledDataflowAlertRef
99
- }
100
- });
101
- const CostInsightsPage = costInsightsPlugin.provide(
102
- createRoutableExtension({
103
- name: "CostInsightsPage",
104
- component: () => import('./index-DaitkcPN.esm.js').then((m) => m.CostInsightsPage),
105
- mountPoint: rootRouteRef
106
- })
107
- );
108
- const EntityCostInsightsContent = costInsightsPlugin.provide(
109
- createRoutableExtension({
110
- name: "EntityCostInsightsContent",
111
- component: () => import('./index-BPzWHt3A.esm.js').then((m) => m.EntityCosts),
112
- mountPoint: rootRouteRef
113
- })
114
- );
115
- const CostInsightsProjectGrowthInstructionsPage = costInsightsPlugin.provide(
116
- createRoutableExtension({
117
- name: "CostInsightsProjectGrowthInstructionsPage",
118
- component: () => import('./index-BTIHr4n_.esm.js').then(
119
- (m) => m.ProjectGrowthInstructionsPage
120
- ),
121
- mountPoint: projectGrowthAlertRef
122
- })
123
- );
124
- const CostInsightsLabelDataflowInstructionsPage = costInsightsPlugin.provide(
125
- createRoutableExtension({
126
- name: "CostInsightsLabelDataflowInstructionsPage",
127
- component: () => import('./index-BA6m_qFS.esm.js').then(
128
- (m) => m.LabelDataflowInstructionsPage
129
- ),
130
- mountPoint: unlabeledDataflowAlertRef
131
- })
132
- );
133
-
134
- var Duration = /* @__PURE__ */ ((Duration2) => {
135
- Duration2["P7D"] = "P7D";
136
- Duration2["P30D"] = "P30D";
137
- Duration2["P90D"] = "P90D";
138
- Duration2["P3M"] = "P3M";
139
- return Duration2;
140
- })(Duration || {});
141
-
142
- var AlertStatus = /* @__PURE__ */ ((AlertStatus2) => {
143
- AlertStatus2["Snoozed"] = "snoozed";
144
- AlertStatus2["Accepted"] = "accepted";
145
- AlertStatus2["Dismissed"] = "dismissed";
146
- return AlertStatus2;
147
- })(AlertStatus || {});
148
- var AlertDismissReason = /* @__PURE__ */ ((AlertDismissReason2) => {
149
- AlertDismissReason2["Other"] = "other";
150
- AlertDismissReason2["Resolved"] = "resolved";
151
- AlertDismissReason2["Expected"] = "expected";
152
- AlertDismissReason2["Seasonal"] = "seasonal";
153
- AlertDismissReason2["Migration"] = "migration";
154
- AlertDismissReason2["NotApplicable"] = "not-applicable";
155
- return AlertDismissReason2;
156
- })(AlertDismissReason || {});
157
- const AlertDismissOptions = [
158
- {
159
- reason: "resolved" /* Resolved */,
160
- label: "This action item is now resolved."
161
- },
162
- {
163
- reason: "seasonal" /* Seasonal */,
164
- label: "This is an expected increase at this time of year."
165
- },
166
- {
167
- reason: "migration" /* Migration */,
168
- label: "This increase is from a migration in process."
169
- },
170
- {
171
- reason: "expected" /* Expected */,
172
- label: "This is an expected increase due to our team\u2019s priorities."
173
- },
174
- {
175
- reason: "not-applicable" /* NotApplicable */,
176
- label: "This action item doesn\u2019t make sense for my team."
177
- },
178
- {
179
- reason: "other" /* Other */,
180
- label: "Other (please specify)"
181
- }
182
- ];
183
- const AlertSnoozeOptions = [
184
- {
185
- duration: Duration.P7D,
186
- label: "1 Week"
187
- },
188
- {
189
- duration: Duration.P30D,
190
- label: "1 Month"
191
- },
192
- {
193
- duration: Duration.P90D,
194
- label: "1 Quarter"
195
- }
196
- ];
197
- var DataKey = /* @__PURE__ */ ((DataKey2) => {
198
- DataKey2["Previous"] = "previous";
199
- DataKey2["Current"] = "current";
200
- DataKey2["Name"] = "name";
201
- return DataKey2;
202
- })(DataKey || {});
203
-
204
- const EngineerThreshold = 0.5;
205
- var ChangeThreshold = /* @__PURE__ */ ((ChangeThreshold2) => {
206
- ChangeThreshold2[ChangeThreshold2["upper"] = 0.05] = "upper";
207
- ChangeThreshold2[ChangeThreshold2["lower"] = -0.05] = "lower";
208
- return ChangeThreshold2;
209
- })(ChangeThreshold || {});
210
- var GrowthType = /* @__PURE__ */ ((GrowthType2) => {
211
- GrowthType2[GrowthType2["Negligible"] = 0] = "Negligible";
212
- GrowthType2[GrowthType2["Savings"] = 1] = "Savings";
213
- GrowthType2[GrowthType2["Excess"] = 2] = "Excess";
214
- return GrowthType2;
215
- })(GrowthType || {});
216
-
217
- var CurrencyType = /* @__PURE__ */ ((CurrencyType2) => {
218
- CurrencyType2["USD"] = "USD";
219
- CurrencyType2["CarbonOffsetTons"] = "CARBON_OFFSET_TONS";
220
- CurrencyType2["Beers"] = "BEERS";
221
- CurrencyType2["IceCream"] = "PINTS_OF_ICE_CREAM";
222
- return CurrencyType2;
223
- })(CurrencyType || {});
224
-
225
- const DEFAULT_DATE_FORMAT = "yyyy-LL-dd";
226
-
227
- var IconType = /* @__PURE__ */ ((IconType2) => {
228
- IconType2["Compute"] = "compute";
229
- IconType2["Data"] = "data";
230
- IconType2["Database"] = "database";
231
- IconType2["Storage"] = "storage";
232
- IconType2["Search"] = "search";
233
- IconType2["ML"] = "ml";
234
- return IconType2;
235
- })(IconType || {});
236
-
237
- var DefaultNavigation = /* @__PURE__ */ ((DefaultNavigation2) => {
238
- DefaultNavigation2["CostOverviewCard"] = "cost-overview-card";
239
- DefaultNavigation2["AlertInsightsHeader"] = "alert-insights-header";
240
- return DefaultNavigation2;
241
- })(DefaultNavigation || {});
242
- const getDefaultNavigationItems = (alerts) => {
243
- const items = [
244
- {
245
- navigation: "cost-overview-card" /* CostOverviewCard */,
246
- icon: /* @__PURE__ */ React.createElement(MoneyIcon, null),
247
- title: "Cost Overview"
248
- }
249
- ];
250
- if (alerts > 0) {
251
- items.push({
252
- navigation: "alert-insights-header" /* AlertInsightsHeader */,
253
- icon: /* @__PURE__ */ React.createElement(ActionIcon, null),
254
- title: "Action Items"
255
- });
256
- }
257
- return items;
258
- };
259
- function getIcon(icon) {
260
- switch (icon) {
261
- case IconType.Compute:
262
- return /* @__PURE__ */ React.createElement(Settings, null);
263
- case IconType.Data:
264
- return /* @__PURE__ */ React.createElement(AccountTree, null);
265
- case IconType.Database:
266
- return /* @__PURE__ */ React.createElement(ViewHeadline, null);
267
- case IconType.Storage:
268
- return /* @__PURE__ */ React.createElement(Storage, null);
269
- case IconType.Search:
270
- return /* @__PURE__ */ React.createElement(Search, null);
271
- case IconType.ML:
272
- return /* @__PURE__ */ React.createElement(School, null);
273
- default:
274
- return /* @__PURE__ */ React.createElement(CloudQueue, null);
275
- }
276
- }
277
-
278
- function validateMetrics(metrics) {
279
- const defaults = metrics.filter((metric) => metric.default);
280
- if (defaults.length > 1) {
281
- throw new Error(
282
- `Only one default metric can be set at a time. Found ${defaults.length}`
283
- );
284
- }
285
- }
286
- function validateCurrencies(currencies) {
287
- const withoutKinds = currencies.filter((currency) => currency.kind === null);
288
- if (withoutKinds.length > 1) {
289
- throw new Error(
290
- `Only one currency can be without kind. Found ${withoutKinds.length}`
291
- );
292
- }
293
- }
294
-
295
- function notEmpty(value) {
296
- return !isNull(value) && !isUndefined(value);
297
- }
298
- function isUndefined(value) {
299
- return value === void 0;
300
- }
301
- function isNull(value) {
302
- return value === null;
303
- }
304
- function assertNever$7(x) {
305
- throw new Error(`Exhaustiveness check failed: ${x}`);
306
- }
307
- function assertAlways(argument) {
308
- if (argument === void 0) {
309
- throw new TypeError(
310
- "Expected to always find a value but received undefined"
311
- );
312
- }
313
- return argument;
314
- }
315
- function findAlways(collection, callback) {
316
- return assertAlways(collection.find(callback));
317
- }
318
- function findAnyKey(record) {
319
- return Object.keys(record != null ? record : {}).find((_) => true);
320
- }
321
-
322
- const rateOf = (cost, duration) => {
323
- switch (duration) {
324
- case Duration.P30D:
325
- return cost / 12;
326
- case Duration.P7D:
327
- case Duration.P90D:
328
- case Duration.P3M:
329
- return cost / 4;
330
- default:
331
- return assertNever$7(duration);
332
- }
333
- };
334
- const defaultCurrencies = [
335
- {
336
- kind: null,
337
- label: "Engineers \u{1F6E0}",
338
- unit: "engineer"
339
- },
340
- {
341
- kind: CurrencyType.USD,
342
- label: "US Dollars \u{1F4B5}",
343
- unit: "dollar",
344
- prefix: "$",
345
- rate: 1
346
- },
347
- {
348
- kind: CurrencyType.CarbonOffsetTons,
349
- label: "Carbon Offset Tons \u267B\uFE0F\u2696\uFE0Fs",
350
- unit: "carbon offset ton",
351
- rate: 3.5
352
- },
353
- {
354
- kind: CurrencyType.Beers,
355
- label: "Beers \u{1F37A}",
356
- unit: "beer",
357
- rate: 4.5
358
- },
359
- {
360
- kind: CurrencyType.IceCream,
361
- label: "Pints of Ice Cream \u{1F366}",
362
- unit: "ice cream pint",
363
- rate: 5.5
364
- }
365
- ];
366
- const createCurrencyFormat = (currency = "USD", locale = "en-US") => new Intl.NumberFormat(locale, {
367
- currency,
368
- style: "currency"
369
- });
370
-
371
- const ConfigContext = createContext(
372
- void 0
373
- );
374
- const defaultState = {
375
- baseCurrency: createCurrencyFormat(),
376
- metrics: [],
377
- products: [],
378
- icons: [],
379
- engineerCost: 0,
380
- engineerThreshold: EngineerThreshold,
381
- hideTrendLine: false,
382
- currencies: defaultCurrencies
383
- };
384
- const ConfigProvider = ({ children }) => {
385
- const c = useApi(configApiRef);
386
- const [config, setConfig] = useState(defaultState);
387
- const [loading, setLoading] = useState(true);
388
- useEffect(() => {
389
- function getProducts() {
390
- const products = c.getOptionalConfig("costInsights.products");
391
- if (products) {
392
- return products.keys().map((key) => ({
393
- kind: key,
394
- name: products.getString(`${key}.name`),
395
- aggregation: [0, 0]
396
- }));
397
- }
398
- return [];
399
- }
400
- function getMetrics() {
401
- const metrics = c.getOptionalConfig("costInsights.metrics");
402
- if (metrics) {
403
- return metrics.keys().map((key) => {
404
- var _a;
405
- return {
406
- kind: key,
407
- name: metrics.getString(`${key}.name`),
408
- default: (_a = metrics.getOptionalBoolean(`${key}.default`)) != null ? _a : false
409
- };
410
- });
411
- }
412
- return [];
413
- }
414
- function getBaseCurrency() {
415
- const baseCurrency = c.getOptionalConfig("costInsights.baseCurrency");
416
- if (baseCurrency) {
417
- const options = baseCurrency.getOptionalConfig("options");
418
- return new Intl.NumberFormat(
419
- baseCurrency.getOptionalString("locale"),
420
- options ? {
421
- localeMatcher: options.getOptionalString("localeMatcher"),
422
- style: "currency",
423
- currency: options.getOptionalString("currency"),
424
- currencySign: options.getOptionalString("currencySign"),
425
- useGrouping: options.getOptionalBoolean("useGrouping"),
426
- minimumIntegerDigits: options.getOptionalNumber(
427
- "minimumIntegerDigits"
428
- ),
429
- minimumFractionDigits: options.getOptionalNumber(
430
- "minimumFractionDigits"
431
- ),
432
- maximumFractionDigits: options.getOptionalNumber(
433
- "maximumFractionDigits"
434
- ),
435
- minimumSignificantDigits: options.getOptionalNumber(
436
- "minimumSignificantDigits"
437
- ),
438
- maximumSignificantDigits: options.getOptionalNumber(
439
- "maximumSignificantDigits"
440
- )
441
- } : void 0
442
- );
443
- }
444
- return defaultState.baseCurrency;
445
- }
446
- function getCurrencies() {
447
- const currencies = c.getOptionalConfig("costInsights.currencies");
448
- if (currencies) {
449
- return currencies.keys().map((key) => ({
450
- label: currencies.getString(`${key}.label`),
451
- unit: currencies.getString(`${key}.unit`),
452
- kind: currencies.getOptionalString(`${key}.kind`) || null,
453
- prefix: currencies.getOptionalString(`${key}.prefix`),
454
- rate: currencies.getOptionalNumber(`${key}.rate`)
455
- }));
456
- }
457
- return defaultCurrencies;
458
- }
459
- function getIcons() {
460
- const products = c.getOptionalConfig("costInsights.products");
461
- if (products) {
462
- return products.keys().map((k) => ({
463
- kind: k,
464
- component: getIcon(products.getOptionalString(`${k}.icon`))
465
- }));
466
- }
467
- return [];
468
- }
469
- function getEngineerCost() {
470
- return c.getNumber("costInsights.engineerCost");
471
- }
472
- function getEngineerThreshold() {
473
- var _a;
474
- return (_a = c.getOptionalNumber("costInsights.engineerThreshold")) != null ? _a : defaultState.engineerThreshold;
475
- }
476
- function getHideTrendLine() {
477
- var _a;
478
- return (_a = c.getOptionalBoolean("costInsights.hideTrendLine")) != null ? _a : defaultState.hideTrendLine;
479
- }
480
- function getConfig() {
481
- const baseCurrency = getBaseCurrency();
482
- const products = getProducts();
483
- const metrics = getMetrics();
484
- const engineerCost = getEngineerCost();
485
- const engineerThreshold = getEngineerThreshold();
486
- const hideTrendLine = getHideTrendLine();
487
- const icons = getIcons();
488
- const currencies = getCurrencies();
489
- validateMetrics(metrics);
490
- validateCurrencies(currencies);
491
- setConfig((prevState) => ({
492
- ...prevState,
493
- baseCurrency,
494
- metrics,
495
- products,
496
- engineerCost,
497
- engineerThreshold,
498
- hideTrendLine,
499
- icons,
500
- currencies
501
- }));
502
- setLoading(false);
503
- }
504
- getConfig();
505
- }, []);
506
- if (loading) {
507
- return null;
508
- }
509
- return /* @__PURE__ */ React.createElement(ConfigContext.Provider, { value: config }, children);
510
- };
511
- function useConfig() {
512
- const config = useContext(ConfigContext);
513
- return config ? config : assertNever$6();
514
- }
515
- function assertNever$6() {
516
- throw new Error("Cannot use useConfig outside of ConfigProvider");
517
- }
518
-
519
- const CurrencyContext = React.createContext(void 0);
520
- const CurrencyProvider = ({ children }) => {
521
- const config = useConfig();
522
- const currencies = config.currencies;
523
- const engineers = currencies.find((currency2) => currency2.kind === null);
524
- const [currency, setCurrency] = useState(
525
- engineers || currencies[0]
526
- );
527
- return /* @__PURE__ */ React.createElement(CurrencyContext.Provider, { value: { currency, setCurrency } }, children);
528
- };
529
- function useCurrency() {
530
- const context = useContext(CurrencyContext);
531
- if (!context) {
532
- assertNever$5();
533
- }
534
- return [context.currency, context.setCurrency];
535
- }
536
- function assertNever$5() {
537
- throw Error("Cannot use useCurrency outside of CurrencyProvider");
538
- }
539
-
540
- function getDefaultPageFilters(groups) {
541
- return {
542
- group: groups.length ? groups[0].id : null,
543
- project: null,
544
- duration: Duration.P90D,
545
- metric: null
546
- };
547
- }
548
-
549
- const schema = yup.object().shape({
550
- group: yup.string().nullable(),
551
- project: yup.string().nullable()
552
- }).required();
553
- const stringify = (queryParams) => qs.stringify(queryParams, { strictNullHandling: true });
554
- const parse = (queryString) => qs.parse(queryString, { ignoreQueryPrefix: true, strictNullHandling: true });
555
- const validate = (queryString) => {
556
- return schema.validate(parse(queryString), {
557
- stripUnknown: true,
558
- strict: true
559
- });
560
- };
561
- const getInitialPageState = (groups, queryParams = {}) => {
562
- return {
563
- ...getDefaultPageFilters(groups),
564
- ...queryParams.project ? { project: queryParams.project } : {},
565
- ...queryParams.group ? { group: queryParams.group } : {}
566
- };
567
- };
568
- const getInitialProductState = (config) => config.products.map((product) => ({
569
- productType: product.kind,
570
- duration: Duration.P30D
571
- }));
572
-
573
- const costInsightsApiRef = createApiRef({
574
- id: "plugin.costinsights.service"
575
- });
576
-
577
- const DEFAULT_DURATION = Duration.P30D;
578
- function inclusiveStartDateOf(duration, inclusiveEndDate) {
579
- switch (duration) {
580
- case Duration.P7D:
581
- case Duration.P30D:
582
- case Duration.P90D:
583
- return DateTime.fromISO(inclusiveEndDate).minus(
584
- Duration$1.fromISO(duration).plus(Duration$1.fromISO(duration))
585
- ).toFormat(DEFAULT_DATE_FORMAT);
586
- case Duration.P3M:
587
- return DateTime.fromISO(inclusiveEndDate).startOf("quarter").minus(
588
- Duration$1.fromISO(duration).plus(Duration$1.fromISO(duration))
589
- ).toFormat(DEFAULT_DATE_FORMAT);
590
- default:
591
- return assertNever$7(duration);
592
- }
593
- }
594
- function exclusiveEndDateOf(duration, inclusiveEndDate) {
595
- switch (duration) {
596
- case Duration.P7D:
597
- case Duration.P30D:
598
- case Duration.P90D:
599
- return DateTime.fromISO(inclusiveEndDate).plus({ days: 1 }).toFormat(DEFAULT_DATE_FORMAT);
600
- case Duration.P3M:
601
- return DateTime.fromISO(quarterEndDate(inclusiveEndDate)).plus({ days: 1 }).toFormat(DEFAULT_DATE_FORMAT);
602
- default:
603
- return assertNever$7(duration);
604
- }
605
- }
606
- function inclusiveEndDateOf(duration, inclusiveEndDate) {
607
- return DateTime.fromISO(exclusiveEndDateOf(duration, inclusiveEndDate)).minus({ days: 1 }).toFormat(DEFAULT_DATE_FORMAT);
608
- }
609
- function intervalsOf(duration, inclusiveEndDate, repeating = 2) {
610
- return `R${repeating}/${duration}/${exclusiveEndDateOf(
611
- duration,
612
- inclusiveEndDate
613
- )}`;
614
- }
615
- function quarterEndDate(inclusiveEndDate) {
616
- const endDate = DateTime.fromISO(inclusiveEndDate);
617
- const endOfQuarter = endDate.endOf("quarter").toFormat(DEFAULT_DATE_FORMAT);
618
- if (endOfQuarter === inclusiveEndDate) {
619
- return endDate.toFormat(DEFAULT_DATE_FORMAT);
620
- }
621
- return endDate.startOf("quarter").minus({ days: 1 }).toFormat(DEFAULT_DATE_FORMAT);
622
- }
623
-
624
- var DefaultLoadingAction = /* @__PURE__ */ ((DefaultLoadingAction2) => {
625
- DefaultLoadingAction2["UserGroups"] = "user-groups";
626
- DefaultLoadingAction2["LastCompleteBillingDate"] = "billing-date";
627
- DefaultLoadingAction2["CostInsightsInitial"] = "cost-insights-initial";
628
- DefaultLoadingAction2["CostInsightsPage"] = "cost-insights-page";
629
- DefaultLoadingAction2["CostInsightsProducts"] = "cost-insights-products";
630
- DefaultLoadingAction2["CostInsightsAlerts"] = "cost-insights-alerts";
631
- return DefaultLoadingAction2;
632
- })(DefaultLoadingAction || {});
633
- const INITIAL_LOADING_ACTIONS = [
634
- "user-groups" /* UserGroups */,
635
- "cost-insights-initial" /* CostInsightsInitial */,
636
- "cost-insights-products" /* CostInsightsProducts */
637
- ];
638
- const getDefaultState = (loadingActions) => {
639
- return loadingActions.reduce(
640
- (defaultState, action) => ({ ...defaultState, [action]: true }),
641
- {}
642
- );
643
- };
644
- const getResetState = (loadingActions) => {
645
- return loadingActions.reduce(
646
- (defaultState, action) => ({ ...defaultState, [action]: false }),
647
- {}
648
- );
649
- };
650
- const getResetStateWithoutInitial = (loadingActions) => {
651
- return loadingActions.reduce((defaultState, action) => {
652
- const loadingActionState = INITIAL_LOADING_ACTIONS.includes(
653
- action
654
- ) ? false : true;
655
- return { ...defaultState, [action]: loadingActionState };
656
- }, {});
657
- };
658
- const settledResponseOf = (responses) => {
659
- return responses.map(
660
- (response) => response.status === "fulfilled" ? response.value : null
661
- );
662
- };
663
- const initialStatesOf = (products, responses) => {
664
- return products.map((product, index) => ({
665
- entity: responses[index],
666
- product,
667
- duration: DEFAULT_DURATION
668
- }));
669
- };
670
-
671
- const costInsightsLightTheme = {
672
- palette: {
673
- blue: "#509AF5",
674
- lightBlue: "#9BF0E1",
675
- darkBlue: "#4101F5",
676
- magenta: "#DC148C",
677
- yellow: "#FFC864",
678
- tooltip: {
679
- background: "#171717",
680
- color: "#DDD"
681
- },
682
- navigationText: "#b5b5b5",
683
- alertBackground: "rgba(219, 219, 219, 0.13)",
684
- dataViz: [
685
- "#509BF5",
686
- "#4B917D",
687
- "#FF6437",
688
- "#F573A0",
689
- "#F59B23",
690
- "#B49BC8",
691
- "#C39687",
692
- "#A0C3D2",
693
- "#FFC864",
694
- "#BABABA"
695
- ]
696
- }
697
- };
698
- const costInsightsDarkTheme = {
699
- palette: {
700
- blue: "#77b8f9",
701
- lightBlue: "#d8f9f4",
702
- darkBlue: "#b595fd",
703
- magenta: "#ee93cd",
704
- yellow: "#fff2da",
705
- tooltip: {
706
- background: "#EEE",
707
- color: "#424242"
708
- },
709
- navigationText: "#b5b5b5",
710
- alertBackground: "rgba(32, 32, 32, 0.13)",
711
- dataViz: [
712
- "#8accff",
713
- "#7bc2ac",
714
- "#ff9664",
715
- "#ffa5d1",
716
- "#ffcc57",
717
- "#e6ccfb",
718
- "#f7c7b7",
719
- "#d2f6ff",
720
- "#fffb94",
721
- "#ececec"
722
- ]
723
- }
724
- };
725
- function brighten(color, coefficient = 0.2) {
726
- return getLuminance(color) > 0.5 ? lighten(color, coefficient) : darken(color, coefficient);
727
- }
728
- const useCostOverviewStyles = (theme) => ({
729
- axis: {
730
- fill: theme.palette.text.primary
731
- },
732
- container: {
733
- height: 450,
734
- width: 1200
735
- },
736
- cartesianGrid: {
737
- stroke: theme.palette.textVerySubtle
738
- },
739
- chart: {
740
- margin: {
741
- right: 30,
742
- top: 16
743
- }
744
- },
745
- yAxis: {
746
- width: 75
747
- }
748
- });
749
- const useOverviewTabsStyles = makeStyles(
750
- (theme) => ({
751
- default: {
752
- padding: theme.spacing(2),
753
- fontWeight: theme.typography.fontWeightBold,
754
- color: theme.palette.text.secondary,
755
- textTransform: "uppercase"
756
- },
757
- selected: {
758
- color: theme.palette.text.primary
759
- }
760
- })
761
- );
762
- const useBarChartStyles = (theme) => ({
763
- axis: {
764
- fill: theme.palette.text.primary
765
- },
766
- barChart: {
767
- margin: {
768
- left: 16,
769
- right: 16
770
- }
771
- },
772
- cartesianGrid: {
773
- stroke: theme.palette.textVerySubtle
774
- },
775
- cursor: {
776
- fill: theme.palette.textVerySubtle,
777
- fillOpacity: 0.3
778
- },
779
- container: {
780
- height: 400,
781
- width: 1200
782
- },
783
- infoIcon: {
784
- marginLeft: 2,
785
- fontSize: "1.25em"
786
- },
787
- xAxis: {
788
- height: 50
789
- }
790
- });
791
- const useBarChartLayoutStyles = makeStyles(
792
- (theme) => createStyles({
793
- wrapper: {
794
- display: "flex",
795
- flexDirection: "column"
796
- },
797
- legend: {
798
- paddingBottom: theme.spacing(2)
799
- }
800
- })
801
- );
802
- const useBarChartStepperButtonStyles = makeStyles(
803
- (theme) => createStyles({
804
- root: {
805
- ...theme.typography.button,
806
- boxSizing: "border-box",
807
- transition: theme.transitions.create(
808
- ["background-color", "box-shadow", "border"],
809
- {
810
- duration: theme.transitions.duration.short
811
- }
812
- ),
813
- borderRadius: "50%",
814
- padding: 0,
815
- width: 40,
816
- height: 40,
817
- boxShadow: theme.shadows[6],
818
- "&:active": {
819
- boxShadow: theme.shadows[12]
820
- },
821
- color: theme.palette.text.primary,
822
- backgroundColor: lighten(theme.palette.background.default, 0.1),
823
- "&:hover": {
824
- backgroundColor: lighten(theme.palette.background.default, 0.2),
825
- textDecoration: "none"
826
- }
827
- }
828
- })
829
- );
830
- const useBarChartLabelStyles = makeStyles(
831
- (theme) => createStyles({
832
- foreignObject: {
833
- textAlign: "center"
834
- },
835
- label: {
836
- fontWeight: theme.typography.fontWeightBold,
837
- display: "block",
838
- textDecoration: "none",
839
- overflow: "hidden",
840
- textOverflow: "ellipsis",
841
- whiteSpace: "nowrap"
842
- },
843
- infoIcon: {
844
- marginLeft: 2,
845
- fontSize: "1.25em"
846
- },
847
- button: {
848
- textTransform: "none",
849
- fontWeight: theme.typography.fontWeightBold,
850
- fontSize: theme.typography.fontSize
851
- }
852
- })
853
- );
854
- const useCostInsightsStyles = makeStyles((theme) => ({
855
- h6Subtle: {
856
- ...theme.typography.h6,
857
- fontWeight: "normal",
858
- color: theme.palette.textSubtle
859
- }
860
- }));
861
- const useCostInsightsTabsStyles = makeStyles((theme) => ({
862
- tabs: {
863
- borderBottom: `1px solid ${theme.palette.textVerySubtle}`,
864
- backgroundColor: brighten(theme.palette.background.default),
865
- padding: theme.spacing(0, 4)
866
- },
867
- tab: {
868
- minHeight: 68,
869
- minWidth: 180,
870
- padding: theme.spacing(1, 4),
871
- "&:hover": {
872
- color: "inherit",
873
- backgroundColor: "inherit"
874
- }
875
- },
876
- indicator: {
877
- backgroundColor: theme.palette.navigation.indicator,
878
- height: 4
879
- },
880
- tabLabel: {
881
- display: "flex",
882
- alignItems: "center"
883
- },
884
- tabLabelText: {
885
- fontSize: theme.typography.fontSize,
886
- fontWeight: theme.typography.fontWeightBold
887
- },
888
- menuItem: {
889
- display: "flex",
890
- justifyContent: "space-between",
891
- alignItems: "center",
892
- minWidth: 180,
893
- padding: theme.spacing(2, 2)
894
- },
895
- menuItemSelected: {
896
- backgroundColor: lighten(theme.palette.background.default, 0.3)
897
- }
898
- }));
899
- makeStyles(
900
- (theme) => createStyles({
901
- root: {
902
- paddingBottom: theme.spacing(2),
903
- borderRadius: "unset"
904
- },
905
- title: {
906
- fontSize: theme.typography.fontSize,
907
- fontWeight: theme.typography.fontWeightBold,
908
- lineHeight: 1.5
909
- },
910
- action: {
911
- margin: 0
912
- }
913
- })
914
- );
915
- const useCostGrowthStyles = makeStyles(
916
- (theme) => createStyles({
917
- excess: {
918
- color: theme.palette.status.error
919
- },
920
- savings: {
921
- color: theme.palette.status.ok
922
- },
923
- indicator: {
924
- display: "flex",
925
- flexDirection: "row",
926
- alignItems: "center",
927
- justifyContent: "flex-end"
928
- }
929
- })
930
- );
931
- const useCostGrowthLegendStyles = makeStyles((theme) => ({
932
- h5: {
933
- ...theme.typography.h5,
934
- fontWeight: 500,
935
- padding: 0
936
- },
937
- marker: {
938
- display: "inherit",
939
- marginRight: theme.spacing(1)
940
- },
941
- helpIcon: {
942
- display: "inherit"
943
- },
944
- title: {
945
- ...theme.typography.overline,
946
- fontWeight: 500,
947
- lineHeight: 0,
948
- marginRight: theme.spacing(1),
949
- color: theme.palette.textSubtle
950
- },
951
- tooltip: {
952
- display: "block",
953
- padding: theme.spacing(1),
954
- backgroundColor: theme.palette.navigation.background
955
- },
956
- tooltipText: {
957
- color: theme.palette.background.default,
958
- fontSize: theme.typography.fontSize,
959
- lineHeight: 1.5
960
- }
961
- }));
962
- const useBarChartStepperStyles = makeStyles(
963
- (theme) => createStyles({
964
- paper: {
965
- display: "flex",
966
- flexDirection: "row",
967
- justifyContent: "space-around",
968
- alignItems: "center",
969
- background: "transparent",
970
- padding: 8
971
- },
972
- step: {
973
- backgroundColor: theme.palette.action.disabled,
974
- borderRadius: "50%",
975
- width: 9,
976
- height: 9,
977
- margin: "0 2px"
978
- },
979
- stepActive: {
980
- backgroundColor: theme.palette.primary.main
981
- },
982
- steps: {
983
- display: "flex",
984
- flexDirection: "row"
985
- },
986
- backButton: {
987
- position: "absolute",
988
- left: 0,
989
- top: "calc(50% - 60px)",
990
- zIndex: 100
991
- },
992
- nextButton: {
993
- position: "absolute",
994
- right: 0,
995
- top: "calc(50% - 60px)",
996
- zIndex: 100
997
- }
998
- })
999
- );
1000
- const useNavigationStyles = makeStyles(
1001
- (theme) => createStyles({
1002
- menuList: {
1003
- borderRadius: theme.shape.borderRadius,
1004
- backgroundColor: theme.palette.navigation.background,
1005
- minWidth: 250
1006
- },
1007
- menuItem: {
1008
- background: "transparent",
1009
- border: 0,
1010
- textTransform: "none",
1011
- width: "100%",
1012
- minHeight: theme.spacing(6),
1013
- margin: theme.spacing(0.5, 2, 0.5, 0)
1014
- },
1015
- listItemIcon: {
1016
- minWidth: 40
1017
- },
1018
- navigationIcon: {
1019
- fill: theme.palette.navigationText
1020
- },
1021
- title: {
1022
- whiteSpace: "nowrap",
1023
- lineHeight: 1,
1024
- color: theme.palette.navigationText,
1025
- fontWeight: theme.typography.fontWeightBold
1026
- }
1027
- })
1028
- );
1029
- const useTooltipStyles = makeStyles(
1030
- (theme) => createStyles({
1031
- tooltip: {
1032
- backgroundColor: theme.palette.tooltip.background,
1033
- borderRadius: theme.shape.borderRadius,
1034
- boxShadow: theme.shadows[1],
1035
- color: theme.palette.tooltip.color,
1036
- fontSize: theme.typography.fontSize,
1037
- minWidth: 300
1038
- },
1039
- maxWidth: {
1040
- maxWidth: 300
1041
- },
1042
- actions: {
1043
- padding: theme.spacing(2)
1044
- },
1045
- header: {
1046
- padding: theme.spacing(2)
1047
- },
1048
- content: {
1049
- padding: theme.spacing(2)
1050
- },
1051
- lensIcon: {
1052
- fontSize: `.75rem`
1053
- },
1054
- divider: {
1055
- backgroundColor: emphasize(theme.palette.divider, 1)
1056
- },
1057
- truncate: {
1058
- maxWidth: 200,
1059
- whiteSpace: "nowrap",
1060
- overflow: "hidden",
1061
- textOverflow: "ellipsis"
1062
- },
1063
- subtitle: {
1064
- fontStyle: "italic"
1065
- }
1066
- })
1067
- );
1068
- const useAlertInsightsSectionStyles = makeStyles(
1069
- (theme) => createStyles({
1070
- button: {
1071
- backgroundColor: theme.palette.textVerySubtle,
1072
- color: theme.palette.text.primary
1073
- }
1074
- })
1075
- );
1076
- const useSelectStyles = makeStyles(
1077
- (theme) => createStyles({
1078
- select: {
1079
- minWidth: 200,
1080
- textAlign: "start",
1081
- backgroundColor: theme.palette.background.paper
1082
- },
1083
- menuItem: {
1084
- minWidth: 200,
1085
- padding: theme.spacing(2),
1086
- "&.compact": {
1087
- padding: theme.spacing(1, 2)
1088
- }
1089
- }
1090
- })
1091
- );
1092
- const useActionItemCardStyles = makeStyles(
1093
- (theme) => createStyles({
1094
- card: {
1095
- boxShadow: "none"
1096
- },
1097
- avatar: {
1098
- backgroundColor: theme.palette.textVerySubtle,
1099
- color: theme.palette.text.primary
1100
- },
1101
- root: {
1102
- minHeight: 80,
1103
- paddingBottom: theme.spacing(2),
1104
- borderRadius: theme.shape.borderRadius
1105
- },
1106
- activeRoot: {
1107
- cursor: "pointer",
1108
- transition: theme.transitions.create("background", {
1109
- duration: theme.transitions.duration.short
1110
- }),
1111
- "&:hover": {
1112
- background: theme.palette.alertBackground
1113
- }
1114
- },
1115
- action: {
1116
- margin: 0
1117
- },
1118
- title: {
1119
- fontSize: theme.typography.fontSize,
1120
- fontWeight: theme.typography.fontWeightBold
1121
- }
1122
- })
1123
- );
1124
- const useProductInsightsCardStyles = makeStyles(
1125
- (theme) => createStyles({
1126
- root: {
1127
- padding: theme.spacing(2, 2, 2, 2.5)
1128
- // restore backstage default padding
1129
- },
1130
- action: {
1131
- margin: 0
1132
- // unset negative margins
1133
- }
1134
- })
1135
- );
1136
- const useProductInsightsChartStyles = makeStyles(
1137
- (theme) => createStyles({
1138
- indicator: {
1139
- fontWeight: theme.typography.fontWeightBold,
1140
- fontSize: "1.25rem"
1141
- },
1142
- actions: {
1143
- display: "flex",
1144
- justifyContent: "space-between",
1145
- alignItems: "center"
1146
- }
1147
- })
1148
- );
1149
- const useBackdropStyles = makeStyles(
1150
- (theme) => createStyles({
1151
- root: {
1152
- zIndex: theme.zIndex.modal
1153
- }
1154
- })
1155
- );
1156
- const useSubtleTypographyStyles = makeStyles(
1157
- (theme) => createStyles({
1158
- root: {
1159
- color: theme.palette.textSubtle
1160
- }
1161
- })
1162
- );
1163
- const useEntityDialogStyles = makeStyles(
1164
- (theme) => createStyles({
1165
- dialogContent: {
1166
- padding: 0
1167
- },
1168
- dialogTitle: {
1169
- padding: 0
1170
- },
1171
- closeButton: {
1172
- position: "absolute",
1173
- right: theme.spacing(1),
1174
- top: theme.spacing(1),
1175
- color: theme.palette.grey[500],
1176
- zIndex: 100
1177
- },
1178
- row: {
1179
- fontSize: theme.typography.fontSize * 1.25
1180
- },
1181
- rowTotal: {
1182
- fontWeight: theme.typography.fontWeightBold
1183
- },
1184
- colFirst: {
1185
- paddingLeft: theme.spacing(2)
1186
- },
1187
- colLast: {
1188
- paddingRight: theme.spacing(2)
1189
- },
1190
- column: {
1191
- fontWeight: theme.typography.fontWeightBold
1192
- },
1193
- growth: {
1194
- display: "flex",
1195
- flexDirection: "row",
1196
- alignContent: "center",
1197
- justifyContent: "flex-end"
1198
- }
1199
- })
1200
- );
1201
- const useAlertDialogStyles = makeStyles(
1202
- (theme) => createStyles({
1203
- content: {
1204
- padding: theme.spacing(0, 5, 2, 5)
1205
- },
1206
- actions: {
1207
- padding: theme.spacing(2, 5)
1208
- },
1209
- radio: {
1210
- margin: theme.spacing(-0.5, 0, -0.5, 0)
1211
- },
1212
- icon: {
1213
- color: theme.palette.primary.dark,
1214
- margin: theme.spacing(2.5, 2.5, 0, 0),
1215
- padding: 0
1216
- }
1217
- })
1218
- );
1219
- const useAlertStatusSummaryButtonStyles = makeStyles(() => ({
1220
- icon: {
1221
- transform: "transform 5s"
1222
- },
1223
- clicked: {
1224
- transform: "rotate(180deg)"
1225
- }
1226
- }));
1227
-
1228
- const LoadingContext = createContext(
1229
- void 0
1230
- );
1231
- function reducer(prevState, action) {
1232
- return {
1233
- ...prevState,
1234
- ...action
1235
- };
1236
- }
1237
- const LoadingProvider = ({ children }) => {
1238
- const classes = useBackdropStyles();
1239
- const { products } = useConfig();
1240
- const actions = useMemo(
1241
- () => INITIAL_LOADING_ACTIONS.filter(
1242
- (action) => products.length || action !== DefaultLoadingAction.CostInsightsProducts
1243
- ),
1244
- [products]
1245
- );
1246
- const [state, dispatch] = useReducer(reducer, getDefaultState(actions));
1247
- const [isBackdropVisible, setBackdropVisible] = useState(false);
1248
- useEffect(() => {
1249
- function displayLoadingBackdrop() {
1250
- setBackdropVisible(
1251
- !state[DefaultLoadingAction.CostInsightsInitial] && Object.values(state).some((l) => l)
1252
- );
1253
- }
1254
- displayLoadingBackdrop();
1255
- }, [state, setBackdropVisible]);
1256
- return /* @__PURE__ */ React.createElement(LoadingContext.Provider, { value: { state, actions, dispatch } }, children, /* @__PURE__ */ React.createElement(Backdrop, { open: isBackdropVisible, classes }, /* @__PURE__ */ React.createElement(CircularProgress, null)));
1257
- };
1258
- function useLoading(mapLoadingToProps) {
1259
- const context = useContext(LoadingContext);
1260
- if (!context) {
1261
- assertNever$4();
1262
- }
1263
- return mapLoadingToProps({
1264
- state: context.state,
1265
- actions: context.actions,
1266
- dispatch: context.dispatch
1267
- });
1268
- }
1269
- function assertNever$4() {
1270
- throw Error("useLoading cannot be used outside of LoadingProvider");
1271
- }
1272
-
1273
- const mapLoadingToProps$2 = ({
1274
- dispatch
1275
- }) => ({
1276
- dispatchLoadingGroups: (isLoading) => dispatch({ [DefaultLoadingAction.UserGroups]: isLoading })
1277
- });
1278
- const GroupsContext = React.createContext(void 0);
1279
- const GroupsProvider = ({ children }) => {
1280
- const identityApi = useApi(identityApiRef);
1281
- const client = useApi(costInsightsApiRef);
1282
- const [error, setError] = useState(null);
1283
- const { dispatchLoadingGroups } = useLoading(mapLoadingToProps$2);
1284
- const [groups, setGroups] = useState(null);
1285
- useEffect(() => {
1286
- dispatchLoadingGroups(true);
1287
- async function getUserGroups() {
1288
- try {
1289
- const { userEntityRef } = await identityApi.getBackstageIdentity();
1290
- const { name: userId } = parseEntityRef(userEntityRef, {
1291
- defaultKind: "User",
1292
- defaultNamespace: DEFAULT_NAMESPACE
1293
- });
1294
- const g = await client.getUserGroups(userId);
1295
- setGroups(g);
1296
- } catch (e) {
1297
- setError(e);
1298
- } finally {
1299
- dispatchLoadingGroups(false);
1300
- }
1301
- }
1302
- getUserGroups();
1303
- }, [client]);
1304
- if (error) {
1305
- return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, error.message);
1306
- }
1307
- if (!groups)
1308
- return null;
1309
- return /* @__PURE__ */ React.createElement(GroupsContext.Provider, { value: { groups } }, children);
1310
- };
1311
- function useGroups() {
1312
- const context = useContext(GroupsContext);
1313
- return context ? context.groups : assertNever$3();
1314
- }
1315
- function assertNever$3() {
1316
- throw Error("Cannot use useGroups outside of GroupsProvider");
1317
- }
1318
-
1319
- const FilterContext = React.createContext(void 0);
1320
- const FilterProvider = ({ children }) => {
1321
- const config = useConfig();
1322
- const navigate = useNavigate();
1323
- const location = useLocation();
1324
- const groups = useGroups();
1325
- const [error, setError] = useState(null);
1326
- const [pageFilters, setPageFilters] = useState(null);
1327
- const [productFilters, setProductFilters] = useState(null);
1328
- useEffect(() => {
1329
- async function setPageFiltersFromLocation() {
1330
- var _a, _b;
1331
- try {
1332
- const queryParams = await validate(location.search);
1333
- const defaultMetric = (_b = (_a = config.metrics.find((m) => m.default)) == null ? void 0 : _a.kind) != null ? _b : null;
1334
- const initialPageState = getInitialPageState(groups, queryParams);
1335
- const initialProductState = getInitialProductState(config);
1336
- setProductFilters(initialProductState);
1337
- setPageFilters({ ...initialPageState, metric: defaultMetric });
1338
- } catch (e) {
1339
- setError(e);
1340
- }
1341
- }
1342
- setPageFiltersFromLocation();
1343
- }, [groups]);
1344
- useEffect(() => {
1345
- function setLocationFromPageFilters(filters) {
1346
- const queryString = stringify({
1347
- group: filters.group,
1348
- ...filters.project ? { project: filters.project } : {}
1349
- });
1350
- navigate({ ...location, search: `?${queryString}` });
1351
- }
1352
- if (pageFilters) {
1353
- setLocationFromPageFilters(pageFilters);
1354
- }
1355
- }, [pageFilters]);
1356
- if (error) {
1357
- return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, error.message);
1358
- }
1359
- if (!pageFilters || !productFilters) {
1360
- return null;
1361
- }
1362
- return /* @__PURE__ */ React.createElement(
1363
- FilterContext.Provider,
1364
- {
1365
- value: { pageFilters, productFilters, setPageFilters, setProductFilters }
1366
- },
1367
- children
1368
- );
1369
- };
1370
- function useFilters(mapFiltersToProps) {
1371
- const context = useContext(FilterContext);
1372
- return context ? mapFiltersToProps(context) : assertNever$2();
1373
- }
1374
- function assertNever$2() {
1375
- throw Error("Cannot use useFilters outside of FilterProvider");
1376
- }
1377
-
1378
- const ScrollContext = React.createContext(void 0);
1379
- const ScrollProvider = ({ children }) => {
1380
- const [scroll, setScroll] = useState(null);
1381
- return /* @__PURE__ */ React.createElement(ScrollContext.Provider, { value: { scroll, setScroll } }, children);
1382
- };
1383
- var ScrollType = /* @__PURE__ */ ((ScrollType2) => {
1384
- ScrollType2["AlertSummary"] = "alert-status-summary";
1385
- return ScrollType2;
1386
- })(ScrollType || {});
1387
- function useScroll() {
1388
- const context = useContext(ScrollContext);
1389
- if (!context) {
1390
- assertNever$1();
1391
- }
1392
- return [context.scroll, context.setScroll];
1393
- }
1394
- function assertNever$1() {
1395
- throw new Error(`Cannot use useScroll outside ScrollProvider`);
1396
- }
1397
-
1398
- const mapLoadingToProps$1 = ({
1399
- dispatch
1400
- }) => ({
1401
- dispatchLoadingBillingDate: (isLoading) => dispatch({ [DefaultLoadingAction.LastCompleteBillingDate]: isLoading })
1402
- });
1403
- const BillingDateContext = React.createContext(void 0);
1404
- const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
1405
- const dateFormatSchema = yup.string().matches(dateRegex, {
1406
- message: "Unsupported billing date format: ${value}. Date should be in YYYY-MM-DD format.",
1407
- excludeEmptyString: true
1408
- });
1409
- const BillingDateProvider = ({ children }) => {
1410
- const client = useApi(costInsightsApiRef);
1411
- const [error, setError] = useState(null);
1412
- const { dispatchLoadingBillingDate } = useLoading(mapLoadingToProps$1);
1413
- const [lastCompleteBillingDate, setLastCompeteBillingDate] = useState(null);
1414
- useEffect(() => {
1415
- dispatchLoadingBillingDate(true);
1416
- async function getLastCompleteBillingDate() {
1417
- try {
1418
- const d = await client.getLastCompleteBillingDate();
1419
- const validDate = await dateFormatSchema.validate(d);
1420
- if (validDate)
1421
- setLastCompeteBillingDate(validDate);
1422
- } catch (e) {
1423
- setError(e);
1424
- } finally {
1425
- dispatchLoadingBillingDate(false);
1426
- }
1427
- }
1428
- getLastCompleteBillingDate();
1429
- }, [client]);
1430
- if (error) {
1431
- return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, error.message);
1432
- }
1433
- if (!lastCompleteBillingDate)
1434
- return null;
1435
- return /* @__PURE__ */ React.createElement(
1436
- BillingDateContext.Provider,
1437
- {
1438
- value: {
1439
- lastCompleteBillingDate
1440
- }
1441
- },
1442
- children
1443
- );
1444
- };
1445
- function useLastCompleteBillingDate() {
1446
- const context = useContext(BillingDateContext);
1447
- return context ? context.lastCompleteBillingDate : assertNever();
1448
- }
1449
- function assertNever() {
1450
- throw Error(
1451
- "Cannot use useLastCompleteBillingDate outside of BillingDateProvider"
1452
- );
1453
- }
1454
-
1455
- const AlertAcceptForm = forwardRef(({ onSubmit, disableSubmit }, ref) => {
1456
- const [checked, setChecked] = useState(false);
1457
- const onFormSubmit = (e) => {
1458
- e.preventDefault();
1459
- onSubmit(null);
1460
- };
1461
- const onChecked = (_, isChecked) => {
1462
- setChecked(isChecked);
1463
- disableSubmit(!isChecked);
1464
- };
1465
- return /* @__PURE__ */ React.createElement("form", { ref, onSubmit: onFormSubmit }, /* @__PURE__ */ React.createElement(FormControl, { component: "fieldset", fullWidth: true }, /* @__PURE__ */ React.createElement(
1466
- FormControlLabel,
1467
- {
1468
- label: "My team can commit to making this change soon, or has already.",
1469
- value: checked,
1470
- control: /* @__PURE__ */ React.createElement(Checkbox, { color: "primary", checked, onChange: onChecked })
1471
- }
1472
- )));
1473
- });
1474
-
1475
- const AlertSnoozeForm = forwardRef(({ onSubmit, disableSubmit }, ref) => {
1476
- const classes = useAlertDialogStyles();
1477
- const [duration, setDuration] = useState(Duration.P7D);
1478
- useEffect(() => disableSubmit(false), [disableSubmit]);
1479
- const onFormSubmit = (e) => {
1480
- e.preventDefault();
1481
- if (duration) {
1482
- const repeatInterval = 1;
1483
- const today = DateTime.now().toFormat(DEFAULT_DATE_FORMAT);
1484
- onSubmit({
1485
- intervals: intervalsOf(duration, today, repeatInterval)
1486
- });
1487
- }
1488
- };
1489
- const onSnoozeDurationChange = (_, value) => {
1490
- setDuration(value);
1491
- };
1492
- return /* @__PURE__ */ React.createElement("form", { ref, onSubmit: onFormSubmit }, /* @__PURE__ */ React.createElement(FormControl, { component: "fieldset", fullWidth: true }, /* @__PURE__ */ React.createElement(Typography, { color: "textPrimary" }, /* @__PURE__ */ React.createElement("b", null, "For how long?")), /* @__PURE__ */ React.createElement(Box, { mb: 1 }, /* @__PURE__ */ React.createElement(
1493
- RadioGroup,
1494
- {
1495
- name: "snooze-alert-options",
1496
- value: duration,
1497
- onChange: onSnoozeDurationChange
1498
- },
1499
- AlertSnoozeOptions.map((option) => /* @__PURE__ */ React.createElement(
1500
- FormControlLabel,
1501
- {
1502
- key: `snooze-alert-option-${option.duration}`,
1503
- label: option.label,
1504
- value: option.duration,
1505
- control: /* @__PURE__ */ React.createElement(Radio, { className: classes.radio })
1506
- }
1507
- ))
1508
- ))));
1509
- });
1510
-
1511
- const AlertDismissForm = forwardRef(({ onSubmit, disableSubmit }, ref) => {
1512
- const classes = useAlertDialogStyles();
1513
- const [other, setOther] = useState(null);
1514
- const [feedback, setFeedback] = useState(null);
1515
- const [reason, setReason] = useState(
1516
- AlertDismissReason.Resolved
1517
- );
1518
- const onFormSubmit = (e) => {
1519
- e.preventDefault();
1520
- if (reason) {
1521
- onSubmit({
1522
- other,
1523
- reason,
1524
- feedback
1525
- });
1526
- }
1527
- };
1528
- const onReasonChange = (_, value) => {
1529
- if (other) {
1530
- setOther(null);
1531
- }
1532
- setReason(value);
1533
- };
1534
- const onOtherChange = (e) => {
1535
- return e.target.value ? setOther(e.target.value) : setOther(null);
1536
- };
1537
- const onFeedbackChange = (e) => {
1538
- return e.target.value ? setFeedback(e.target.value) : setFeedback(null);
1539
- };
1540
- useEffect(() => {
1541
- function validateDismissForm() {
1542
- if (reason === AlertDismissReason.Other) {
1543
- if (other) {
1544
- disableSubmit(false);
1545
- } else {
1546
- disableSubmit(true);
1547
- }
1548
- } else if (reason) {
1549
- disableSubmit(false);
1550
- } else {
1551
- disableSubmit(true);
1552
- }
1553
- }
1554
- validateDismissForm();
1555
- }, [reason, other, disableSubmit]);
1556
- return /* @__PURE__ */ React.createElement("form", { ref, onSubmit: onFormSubmit }, /* @__PURE__ */ React.createElement(FormControl, { component: "fieldset", fullWidth: true }, /* @__PURE__ */ React.createElement(Typography, { color: "textPrimary" }, /* @__PURE__ */ React.createElement("b", null, "Reason for dismissing?")), /* @__PURE__ */ React.createElement(Box, { mb: 1 }, /* @__PURE__ */ React.createElement(
1557
- RadioGroup,
1558
- {
1559
- name: "dismiss-alert-reasons",
1560
- value: reason,
1561
- onChange: onReasonChange
1562
- },
1563
- AlertDismissOptions.map((option) => /* @__PURE__ */ React.createElement(
1564
- FormControlLabel,
1565
- {
1566
- key: `dismiss-alert-option-${option.reason}`,
1567
- label: option.label,
1568
- value: option.reason,
1569
- control: /* @__PURE__ */ React.createElement(Radio, { className: classes.radio })
1570
- }
1571
- ))
1572
- ), /* @__PURE__ */ React.createElement(Collapse, { in: reason === AlertDismissReason.Other }, /* @__PURE__ */ React.createElement(Box, { ml: 4 }, /* @__PURE__ */ React.createElement(
1573
- TextField,
1574
- {
1575
- id: "dismiss-alert-option-other",
1576
- variant: "outlined",
1577
- multiline: true,
1578
- fullWidth: true,
1579
- rows: 4,
1580
- value: other != null ? other : "",
1581
- onChange: onOtherChange
1582
- }
1583
- )))), /* @__PURE__ */ React.createElement(Typography, { gutterBottom: true }, /* @__PURE__ */ React.createElement("b", null, "Any other feedback you can provide?")), /* @__PURE__ */ React.createElement(
1584
- TextField,
1585
- {
1586
- id: "dismiss-alert-feedback",
1587
- variant: "outlined",
1588
- multiline: true,
1589
- rows: 4,
1590
- fullWidth: true,
1591
- value: feedback != null ? feedback : "",
1592
- onChange: onFeedbackChange
1593
- }
1594
- )));
1595
- });
1596
-
1597
- const createAlertHandler = (status) => (alert) => alert.status === status;
1598
- const isAlertActive = (alert) => !hasProperty(alert, "status");
1599
- const isAlertSnoozed = createAlertHandler(AlertStatus.Snoozed);
1600
- const isAlertAccepted = createAlertHandler(AlertStatus.Accepted);
1601
- const isAlertDismissed = createAlertHandler(AlertStatus.Dismissed);
1602
- const createStatusHandler = (status) => (s) => s === status;
1603
- const isStatusSnoozed = createStatusHandler(AlertStatus.Snoozed);
1604
- const isStatusAccepted = createStatusHandler(AlertStatus.Accepted);
1605
- const isStatusDismissed = createStatusHandler(AlertStatus.Dismissed);
1606
- const createAlertEventHandler = (onEvent) => (alert) => hasProperty(alert, onEvent);
1607
- const isSnoozeEnabled = createAlertEventHandler("onSnoozed");
1608
- const isAcceptEnabled = createAlertEventHandler("onAccepted");
1609
- const isDismissEnabled = createAlertEventHandler("onDismissed");
1610
- const createFormEnabledHandler = (Form) => (alert) => {
1611
- if (!alert)
1612
- return false;
1613
- if (alert[Form] === null)
1614
- return false;
1615
- switch (Form) {
1616
- case "SnoozeForm":
1617
- return isSnoozeEnabled(alert);
1618
- case "AcceptForm":
1619
- return isAcceptEnabled(alert);
1620
- case "DismissForm":
1621
- return isDismissEnabled(alert);
1622
- default:
1623
- return false;
1624
- }
1625
- };
1626
- const isSnoozeFormEnabled = createFormEnabledHandler("SnoozeForm");
1627
- const isAcceptFormEnabled = createFormEnabledHandler("AcceptForm");
1628
- const isDismissFormEnabled = createFormEnabledHandler("DismissForm");
1629
- function formOf(alert, status) {
1630
- var _a, _b, _c;
1631
- switch (status) {
1632
- case AlertStatus.Snoozed: {
1633
- const SnoozeForm = (_a = alert == null ? void 0 : alert.SnoozeForm) != null ? _a : AlertSnoozeForm;
1634
- return isSnoozeFormEnabled(alert) ? SnoozeForm : null;
1635
- }
1636
- case AlertStatus.Accepted: {
1637
- const AcceptForm = (_b = alert == null ? void 0 : alert.AcceptForm) != null ? _b : AlertAcceptForm;
1638
- return isAcceptFormEnabled(alert) ? AcceptForm : null;
1639
- }
1640
- case AlertStatus.Dismissed: {
1641
- const DismissForm = (_c = alert == null ? void 0 : alert.DismissForm) != null ? _c : AlertDismissForm;
1642
- return isDismissFormEnabled(alert) ? DismissForm : null;
1643
- }
1644
- default:
1645
- return null;
1646
- }
1647
- }
1648
- function choose$1(status, values, none) {
1649
- switch (status) {
1650
- case AlertStatus.Snoozed:
1651
- return values[0];
1652
- case AlertStatus.Accepted:
1653
- return values[1];
1654
- case AlertStatus.Dismissed:
1655
- return values[2];
1656
- default:
1657
- return none;
1658
- }
1659
- }
1660
- function hasProperty(alert, prop) {
1661
- return prop in (alert != null ? alert : {});
1662
- }
1663
- const sumOfAllAlerts = (sum, alerts) => sum + alerts.length;
1664
-
1665
- const CostInsightsThemeProvider = ({
1666
- children
1667
- }) => {
1668
- return /* @__PURE__ */ React.createElement(
1669
- ThemeProvider,
1670
- {
1671
- theme: (theme) => createTheme({
1672
- ...theme,
1673
- palette: {
1674
- ...theme.palette,
1675
- ...theme.palette.type === "dark" ? costInsightsDarkTheme.palette : costInsightsLightTheme.palette
1676
- }
1677
- })
1678
- },
1679
- children
1680
- );
1681
- };
1682
-
1683
- const useStyles$1 = makeStyles((theme) => ({
1684
- root: {
1685
- gridArea: "pageContent",
1686
- padding: theme.spacing(4)
1687
- }
1688
- }));
1689
- const AlertInstructionsLayout = ({
1690
- title,
1691
- children
1692
- }) => {
1693
- const classes = useStyles$1();
1694
- return /* @__PURE__ */ React.createElement(CostInsightsThemeProvider, null, /* @__PURE__ */ React.createElement(ConfigProvider, null, /* @__PURE__ */ React.createElement(CurrencyProvider, null, /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(
1695
- Header,
1696
- {
1697
- title: "Cost Insights",
1698
- pageTitleOverride: title,
1699
- type: "Tool"
1700
- }
1701
- ), /* @__PURE__ */ React.createElement(Container, { maxWidth: "md", disableGutters: true, className: classes.root }, /* @__PURE__ */ React.createElement(Box, { mb: 3 }, /* @__PURE__ */ React.createElement(
1702
- Button,
1703
- {
1704
- variant: "outlined",
1705
- startIcon: /* @__PURE__ */ React.createElement(ChevronLeftIcon, null),
1706
- href: "/cost-insights"
1707
- },
1708
- "Back to Cost Insights"
1709
- )), children)))));
1710
- };
1711
-
1712
- const BarChartLabel = ({
1713
- x,
1714
- y,
1715
- height,
1716
- width,
1717
- details,
1718
- children
1719
- }) => {
1720
- const classes = useBarChartLabelStyles();
1721
- const translateX = width * -0.5;
1722
- return /* @__PURE__ */ React.createElement(
1723
- "foreignObject",
1724
- {
1725
- className: classes.foreignObject,
1726
- style: { transform: `translateX(${translateX}px)` },
1727
- x,
1728
- y,
1729
- height,
1730
- width
1731
- },
1732
- /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "column", justifyContent: "center" }, /* @__PURE__ */ React.createElement(Typography, { className: classes.label, gutterBottom: true }, children), details)
1733
- );
1734
- };
1735
-
1736
- const BarChartTick = ({
1737
- x,
1738
- y,
1739
- height,
1740
- width,
1741
- payload,
1742
- visibleTicksCount,
1743
- details
1744
- }) => {
1745
- const gutterWidth = 5;
1746
- const labelWidth = width / visibleTicksCount - gutterWidth * 2;
1747
- return /* @__PURE__ */ React.createElement(
1748
- BarChartLabel,
1749
- {
1750
- x,
1751
- y,
1752
- height,
1753
- width: labelWidth,
1754
- details
1755
- },
1756
- !payload.value ? "Unlabeled" : payload.value
1757
- );
1758
- };
1759
-
1760
- const BarChartStepperButton = forwardRef(
1761
- ({
1762
- name,
1763
- children,
1764
- ...buttonBaseProps
1765
- }, ref) => {
1766
- const classes = useBarChartStepperButtonStyles();
1767
- return /* @__PURE__ */ React.createElement(
1768
- ButtonBase,
1769
- {
1770
- ref,
1771
- classes,
1772
- disableRipple: true,
1773
- "data-testid": `bar-chart-stepper-button-${name}`,
1774
- ...buttonBaseProps
1775
- },
1776
- children
1777
- );
1778
- }
1779
- );
1780
-
1781
- const BarChartSteps = ({
1782
- steps,
1783
- activeStep,
1784
- onClick
1785
- }) => {
1786
- const classes = useBarChartStepperStyles();
1787
- const handleOnClick = (index) => (event) => {
1788
- event.preventDefault();
1789
- onClick(index);
1790
- };
1791
- return /* @__PURE__ */ React.createElement("div", { className: classes.steps }, [...new Array(steps)].map((_, index) => /* @__PURE__ */ React.createElement(ButtonBase, { key: index, centerRipple: true, onClick: handleOnClick(index) }, /* @__PURE__ */ React.createElement(
1792
- "div",
1793
- {
1794
- "data-testid": "bar-chart-step",
1795
- className: `${classes.step} ${index === activeStep ? classes.stepActive : ""}`
1796
- }
1797
- ))));
1798
- };
1799
-
1800
- const BarChartStepper = ({
1801
- steps,
1802
- disableScroll,
1803
- onChange
1804
- }) => {
1805
- const classes = useBarChartStepperStyles();
1806
- const [activeStep, setActiveStep] = useState(0);
1807
- const diff = steps % 10;
1808
- const stepsRemaining = steps - activeStep <= diff ? diff : steps;
1809
- const displayedStep = activeStep % 10;
1810
- useEffect(() => {
1811
- onChange(activeStep);
1812
- }, [activeStep, onChange]);
1813
- const handleNext = () => {
1814
- setActiveStep((prevStep) => prevStep + 1);
1815
- };
1816
- const handleBack = () => {
1817
- setActiveStep((prevStep) => prevStep - 1);
1818
- };
1819
- const handleClick = (index) => {
1820
- setActiveStep((prevStep) => {
1821
- const offset = index - prevStep % 10;
1822
- return prevStep + offset;
1823
- });
1824
- };
1825
- return /* @__PURE__ */ React.createElement(
1826
- Paper,
1827
- {
1828
- "data-testid": "bar-chart-stepper",
1829
- square: true,
1830
- elevation: 0,
1831
- className: classes.paper
1832
- },
1833
- /* @__PURE__ */ React.createElement(
1834
- Slide,
1835
- {
1836
- direction: "right",
1837
- in: !disableScroll && activeStep !== 0,
1838
- mountOnEnter: true,
1839
- unmountOnExit: true
1840
- },
1841
- /* @__PURE__ */ React.createElement(
1842
- BarChartStepperButton,
1843
- {
1844
- name: "back",
1845
- className: classes.backButton,
1846
- onClick: handleBack
1847
- },
1848
- /* @__PURE__ */ React.createElement(ChevronLeftIcon, null)
1849
- )
1850
- ),
1851
- /* @__PURE__ */ React.createElement(
1852
- BarChartSteps,
1853
- {
1854
- steps: Math.min(10, stepsRemaining),
1855
- activeStep: displayedStep,
1856
- onClick: handleClick
1857
- }
1858
- ),
1859
- /* @__PURE__ */ React.createElement(
1860
- Slide,
1861
- {
1862
- direction: "left",
1863
- in: !disableScroll && activeStep < steps - 1,
1864
- mountOnEnter: true,
1865
- unmountOnExit: true
1866
- },
1867
- /* @__PURE__ */ React.createElement(
1868
- BarChartStepperButton,
1869
- {
1870
- name: "next",
1871
- className: classes.nextButton,
1872
- onClick: handleNext
1873
- },
1874
- /* @__PURE__ */ React.createElement(ChevronRightIcon, null)
1875
- )
1876
- )
1877
- );
1878
- };
1879
-
1880
- const BarChartTooltip = (props) => {
1881
- const { title, content, subtitle, topRight, actions, children } = props;
1882
- const classes = useTooltipStyles();
1883
- const titleClassName = classnames(classes.truncate, {
1884
- [classes.maxWidth]: topRight === void 0
1885
- });
1886
- return /* @__PURE__ */ React.createElement(Box, { className: classes.tooltip, display: "flex", flexDirection: "column" }, /* @__PURE__ */ React.createElement(
1887
- Box,
1888
- {
1889
- display: "flex",
1890
- flexDirection: "row",
1891
- justifyContent: "space-between",
1892
- alignItems: "baseline",
1893
- px: 2,
1894
- pt: 2
1895
- },
1896
- /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "column" }, /* @__PURE__ */ React.createElement(Typography, { className: titleClassName, variant: "h6" }, title), subtitle && /* @__PURE__ */ React.createElement(Typography, { className: classes.subtitle, variant: "subtitle1" }, subtitle)),
1897
- topRight && /* @__PURE__ */ React.createElement(Box, { ml: 2 }, topRight)
1898
- ), content && /* @__PURE__ */ React.createElement(Box, { px: 2, pt: 2, className: classes.maxWidth }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", paragraph: true }, content)), /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "column", p: 2 }, children), actions && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Divider, { className: classes.divider, variant: "fullWidth" }), /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "column", p: 2 }, actions)));
1899
- };
1900
-
1901
- const BarChartTooltipItem = (props) => {
1902
- const { item } = props;
1903
- const classes = useTooltipStyles();
1904
- return /* @__PURE__ */ React.createElement(
1905
- Box,
1906
- {
1907
- display: "flex",
1908
- alignItems: "center",
1909
- justifyContent: "space-between",
1910
- minHeight: 25
1911
- },
1912
- /* @__PURE__ */ React.createElement(Box, { display: "flex", alignContent: "center", marginRight: ".5em" }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", marginRight: ".5em" }, /* @__PURE__ */ React.createElement(LensIcon, { className: classes.lensIcon, style: { fill: item.fill } })), /* @__PURE__ */ React.createElement(Typography, null, item.label)),
1913
- /* @__PURE__ */ React.createElement(Typography, { display: "block" }, item.value)
1914
- );
1915
- };
1916
-
1917
- const currencyFormatter = (currency) => {
1918
- const options = currency.resolvedOptions();
1919
- return new Intl.NumberFormat(options.locale, {
1920
- style: "currency",
1921
- currency: options.currency,
1922
- minimumFractionDigits: 0,
1923
- maximumFractionDigits: 0
1924
- });
1925
- };
1926
- const lengthyCurrencyFormatter = (currency) => {
1927
- const options = currency.resolvedOptions();
1928
- return new Intl.NumberFormat(options.locale, {
1929
- style: "currency",
1930
- currency: options.currency,
1931
- minimumFractionDigits: 0,
1932
- minimumSignificantDigits: 2,
1933
- maximumSignificantDigits: 2
1934
- });
1935
- };
1936
- const numberFormatter = new Intl.NumberFormat("en-US", {
1937
- minimumFractionDigits: 0,
1938
- maximumFractionDigits: 0
1939
- });
1940
- const monthFormatter = new Intl.DateTimeFormat("en-US", {
1941
- timeZone: "UTC",
1942
- month: "long",
1943
- year: "numeric"
1944
- });
1945
- const dateFormatter = new Intl.DateTimeFormat("en-US", {
1946
- timeZone: "UTC",
1947
- day: "numeric",
1948
- month: "short"
1949
- });
1950
- const monthOf = (date) => {
1951
- return monthFormatter.format(Date.parse(date));
1952
- };
1953
- const quarterOf = (date) => {
1954
- const d = DateTime.fromISO(date).isValid ? DateTime.fromISO(date) : DateTime.fromFormat(date, "yyyy-'Q'q");
1955
- return d.toFormat("'Q'q yyyy");
1956
- };
1957
- function formatCurrency(amount, currency) {
1958
- const n = Math.round(amount);
1959
- const numString = numberFormatter.format(n);
1960
- return currency ? `${numString} ${pluralize(currency, n)}` : numString;
1961
- }
1962
- function formatChange(change, options) {
1963
- if (notEmpty(change.ratio)) {
1964
- return formatPercent(
1965
- (options == null ? void 0 : options.absolute) ? Math.abs(change.ratio) : change.ratio
1966
- );
1967
- }
1968
- if (options == null ? void 0 : options.absolute) {
1969
- return "\u221E";
1970
- }
1971
- return change.amount >= 0 ? "\u221E" : "-\u221E";
1972
- }
1973
- function formatPercent(n) {
1974
- if (isNaN(n) || Math.abs(n) < 0.01) {
1975
- return "0%";
1976
- }
1977
- if (Math.abs(n) > 10) {
1978
- return `>${n < 0 ? "-" : ""}1000%`;
1979
- }
1980
- return `${(n * 100).toFixed(0)}%`;
1981
- }
1982
- function formatLastTwoLookaheadQuarters(inclusiveEndDate) {
1983
- const start = DateTime.fromISO(
1984
- inclusiveStartDateOf(Duration.P3M, inclusiveEndDate)
1985
- ).toFormat("'Q'q yyyy");
1986
- const end = DateTime.fromISO(
1987
- inclusiveEndDateOf(Duration.P3M, inclusiveEndDate)
1988
- ).toFormat("'Q'q yyyy");
1989
- return `${start} vs ${end}`;
1990
- }
1991
- const formatRelativePeriod = (duration, date, isEndDate) => {
1992
- const periodStart = isEndDate ? inclusiveStartDateOf(duration, date) : date;
1993
- const periodEnd = isEndDate ? date : inclusiveEndDateOf(duration, date);
1994
- const days = Duration$1.fromISO(duration).days;
1995
- if (![periodStart, periodEnd].includes(date)) {
1996
- throw new Error(`Invalid relative date ${date} for duration ${duration}`);
1997
- }
1998
- return date === periodStart ? `First ${days} Days` : `Last ${days} Days`;
1999
- };
2000
- function formatPeriod(duration, date, isEndDate) {
2001
- switch (duration) {
2002
- case Duration.P3M:
2003
- return quarterOf(
2004
- isEndDate ? inclusiveEndDateOf(duration, date) : inclusiveStartDateOf(duration, date)
2005
- );
2006
- default:
2007
- return formatRelativePeriod(duration, date, isEndDate);
2008
- }
2009
- }
2010
-
2011
- const aggregationSort = (a, b) => a.date.localeCompare(b.date);
2012
- const resourceSort = (a, b) => b.previous + b.current - (a.previous + a.current);
2013
- function totalAggregationSort(a, b) {
2014
- var _a, _b, _c, _d;
2015
- const [prevA, currA] = (_b = (_a = a.entity) == null ? void 0 : _a.aggregation) != null ? _b : [0, 0];
2016
- const [prevB, currB] = (_d = (_c = b.entity) == null ? void 0 : _c.aggregation) != null ? _d : [0, 0];
2017
- return prevB + currB - (prevA + currA);
2018
- }
2019
-
2020
- const formatGraphValue = (baseCurrency) => (value, _index, format) => {
2021
- if (format === "number") {
2022
- return value.toLocaleString();
2023
- }
2024
- if (value < 1) {
2025
- return lengthyCurrencyFormatter(baseCurrency).format(value);
2026
- }
2027
- return currencyFormatter(baseCurrency).format(value);
2028
- };
2029
- const overviewGraphTickFormatter = (millis) => typeof millis === "number" ? dateFormatter.format(millis) : millis;
2030
- const tooltipItemOf = (baseCurrency) => (payload) => {
2031
- const value = payload.value && !isNaN(Number(payload.value)) ? baseCurrency.format(Number(payload.value)) : payload.value;
2032
- const fill = payload.color;
2033
- switch (payload.dataKey) {
2034
- case DataKey.Current:
2035
- case DataKey.Previous:
2036
- return {
2037
- label: payload.name,
2038
- value,
2039
- fill
2040
- };
2041
- default:
2042
- return null;
2043
- }
2044
- };
2045
- const resourceOf = (entity) => ({
2046
- name: entity.id,
2047
- previous: entity.aggregation[0],
2048
- current: entity.aggregation[1]
2049
- });
2050
- const titleOf = (label) => {
2051
- return label ? String(label) : "Unlabeled";
2052
- };
2053
- const isInvalid = ({ label, payload }) => {
2054
- return label === void 0 || !payload || !payload.length;
2055
- };
2056
- const isLabeled = (data) => {
2057
- return (data == null ? void 0 : data.activeLabel) && (data == null ? void 0 : data.activeLabel) !== "";
2058
- };
2059
- const isUnlabeled = (data) => {
2060
- return (data == null ? void 0 : data.activeLabel) === "";
2061
- };
2062
-
2063
- const defaultTooltip = (baseCurrency) => {
2064
- const tooltip = ({ label, payload = [] }) => {
2065
- if (isInvalid({ label, payload }))
2066
- return null;
2067
- const title = titleOf(label);
2068
- const items = payload.map(tooltipItemOf(baseCurrency)).filter(notEmpty);
2069
- return /* @__PURE__ */ React.createElement(BarChartTooltip, { title }, items.map((item, index) => /* @__PURE__ */ React.createElement(BarChartTooltipItem, { key: `${item.label}-${index}`, item })));
2070
- };
2071
- return tooltip;
2072
- };
2073
- const BarChart = (props) => {
2074
- const { baseCurrency } = useConfig();
2075
- const {
2076
- resources,
2077
- responsive = true,
2078
- displayAmount = 6,
2079
- options = {},
2080
- tooltip = defaultTooltip(baseCurrency),
2081
- onClick,
2082
- onMouseMove
2083
- } = props;
2084
- const theme = useTheme();
2085
- const styles = useBarChartStyles(theme);
2086
- const [activeChart, setActiveChart] = useState(false);
2087
- const [stepWindow, setStepWindow] = useState(() => [0, displayAmount]);
2088
- const data = Object.assign(
2089
- {
2090
- previousFill: theme.palette.lightBlue,
2091
- currentFill: theme.palette.darkBlue,
2092
- previousName: "Previous",
2093
- currentName: "Current"
2094
- },
2095
- options
2096
- );
2097
- const [stepStart, stepEnd] = stepWindow;
2098
- const steps = Math.ceil(resources.length / displayAmount);
2099
- const disableStepper = resources.length <= displayAmount;
2100
- const sortedResources = resources.sort(resourceSort).slice(stepStart, stepEnd);
2101
- const globalResourcesMax = resources.reduce(
2102
- (max, r) => Math.max(max, r.current, r.previous),
2103
- 0
2104
- );
2105
- const onStepChange = useCallback(
2106
- (activeStep) => {
2107
- const start = activeStep * displayAmount;
2108
- const end = start + displayAmount;
2109
- if (end > resources.length) {
2110
- setStepWindow([start, resources.length]);
2111
- } else {
2112
- setStepWindow([start, end]);
2113
- }
2114
- },
2115
- [setStepWindow, resources, displayAmount]
2116
- );
2117
- return /* @__PURE__ */ React.createElement(
2118
- Box,
2119
- {
2120
- position: "relative",
2121
- onMouseLeave: () => setActiveChart(false),
2122
- onMouseEnter: () => setActiveChart(true),
2123
- "data-testid": "bar-chart-wrapper"
2124
- },
2125
- /* @__PURE__ */ React.createElement(
2126
- ResponsiveContainer,
2127
- {
2128
- height: styles.container.height,
2129
- width: responsive ? "100%" : styles.container.width
2130
- },
2131
- /* @__PURE__ */ React.createElement(
2132
- BarChart$1,
2133
- {
2134
- style: { cursor: onClick ? "pointer" : null },
2135
- onClick,
2136
- onMouseMove,
2137
- data: sortedResources,
2138
- margin: styles.barChart.margin,
2139
- barSize: 45,
2140
- "data-testid": "bar-chart"
2141
- },
2142
- tooltip && /* @__PURE__ */ React.createElement(
2143
- Tooltip,
2144
- {
2145
- filterNull: true,
2146
- cursor: styles.cursor,
2147
- animationDuration: 100,
2148
- content: tooltip
2149
- }
2150
- ),
2151
- /* @__PURE__ */ React.createElement(
2152
- CartesianGrid,
2153
- {
2154
- vertical: false,
2155
- stroke: styles.cartesianGrid.stroke
2156
- }
2157
- ),
2158
- /* @__PURE__ */ React.createElement(
2159
- XAxis,
2160
- {
2161
- dataKey: DataKey.Name,
2162
- tickLine: false,
2163
- interval: 0,
2164
- height: styles.xAxis.height,
2165
- tick: BarChartTick
2166
- }
2167
- ),
2168
- /* @__PURE__ */ React.createElement(
2169
- YAxis,
2170
- {
2171
- tickFormatter: currencyFormatter(baseCurrency).format,
2172
- domain: [() => 0, globalResourcesMax],
2173
- tick: styles.axis
2174
- }
2175
- ),
2176
- /* @__PURE__ */ React.createElement(
2177
- Bar,
2178
- {
2179
- dataKey: DataKey.Previous,
2180
- name: data.previousName,
2181
- fill: data.previousFill,
2182
- isAnimationActive: false
2183
- }
2184
- ),
2185
- /* @__PURE__ */ React.createElement(
2186
- Bar,
2187
- {
2188
- dataKey: DataKey.Current,
2189
- name: data.currentName,
2190
- fill: data.currentFill,
2191
- isAnimationActive: false
2192
- }
2193
- )
2194
- )
2195
- ),
2196
- !disableStepper && /* @__PURE__ */ React.createElement(
2197
- BarChartStepper,
2198
- {
2199
- steps,
2200
- disableScroll: !activeChart,
2201
- onChange: onStepChange
2202
- }
2203
- )
2204
- );
2205
- };
2206
-
2207
- const LegendItem = (props) => {
2208
- const { title, tooltipText, markerColor, children } = props;
2209
- const classes = useCostGrowthLegendStyles();
2210
- return /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "column" }, /* @__PURE__ */ React.createElement(
2211
- Box,
2212
- {
2213
- minHeight: 25,
2214
- display: "flex",
2215
- flexDirection: "row",
2216
- alignItems: "center"
2217
- },
2218
- markerColor && /* @__PURE__ */ React.createElement("div", { className: classes.marker }, /* @__PURE__ */ React.createElement(LensIcon, { style: { fontSize: "1em", fill: markerColor } })),
2219
- /* @__PURE__ */ React.createElement(Typography, { className: classes.title, variant: "overline" }, title),
2220
- tooltipText && /* @__PURE__ */ React.createElement(
2221
- Tooltip$1,
2222
- {
2223
- classes: { tooltip: classes.tooltip },
2224
- title: /* @__PURE__ */ React.createElement(Typography, { className: classes.tooltipText }, tooltipText),
2225
- placement: "top-start"
2226
- },
2227
- /* @__PURE__ */ React.createElement(
2228
- Typography,
2229
- {
2230
- component: "span",
2231
- role: "img",
2232
- "aria-label": "help",
2233
- className: classes.helpIcon
2234
- },
2235
- /* @__PURE__ */ React.createElement(HelpOutlineOutlinedIcon, { fontSize: "small" })
2236
- )
2237
- )
2238
- ), /* @__PURE__ */ React.createElement(Box, { marginLeft: markerColor ? "1.5em" : 0 }, /* @__PURE__ */ React.createElement(Typography, { className: classes.h5, variant: "h5" }, children)));
2239
- };
2240
-
2241
- const BarChartLegend = (props) => {
2242
- const { costStart, costEnd, options = {}, children } = props;
2243
- const theme = useTheme();
2244
- const classes = useBarChartLayoutStyles();
2245
- const { baseCurrency } = useConfig();
2246
- const data = Object.assign(
2247
- {
2248
- previousName: "Previous",
2249
- previousFill: theme.palette.lightBlue,
2250
- currentName: "Current",
2251
- currentFill: theme.palette.darkBlue
2252
- },
2253
- options
2254
- );
2255
- return /* @__PURE__ */ React.createElement(Box, { className: classes.legend, display: "flex", flexDirection: "row" }, /* @__PURE__ */ React.createElement(Box, { marginRight: 2 }, /* @__PURE__ */ React.createElement(
2256
- LegendItem,
2257
- {
2258
- title: data.previousName,
2259
- markerColor: options.hideMarker ? void 0 : data.previousFill
2260
- },
2261
- currencyFormatter(baseCurrency).format(costStart)
2262
- )), /* @__PURE__ */ React.createElement(Box, { marginRight: 2 }, /* @__PURE__ */ React.createElement(
2263
- LegendItem,
2264
- {
2265
- title: data.currentName,
2266
- markerColor: options.hideMarker ? void 0 : data.currentFill
2267
- },
2268
- currencyFormatter(baseCurrency).format(costEnd)
2269
- )), children);
2270
- };
2271
-
2272
- function growthOf(change, engineerThreshold) {
2273
- const exceedsEngineerThreshold = Math.abs(change.amount) >= engineerThreshold;
2274
- if (notEmpty(change.ratio)) {
2275
- if (exceedsEngineerThreshold && change.ratio >= ChangeThreshold.upper) {
2276
- return GrowthType.Excess;
2277
- }
2278
- if (exceedsEngineerThreshold && change.ratio <= ChangeThreshold.lower) {
2279
- return GrowthType.Savings;
2280
- }
2281
- } else {
2282
- if (exceedsEngineerThreshold && change.amount > 0) {
2283
- return GrowthType.Excess;
2284
- }
2285
- if (exceedsEngineerThreshold && change.amount < 0) {
2286
- return GrowthType.Savings;
2287
- }
2288
- }
2289
- return GrowthType.Negligible;
2290
- }
2291
- function getComparedChange(dailyCost, metricData, duration, lastCompleteBillingDate) {
2292
- var _a, _b;
2293
- const dailyCostRatio = (_a = dailyCost.change) == null ? void 0 : _a.ratio;
2294
- const metricDataRatio = (_b = metricData.change) == null ? void 0 : _b.ratio;
2295
- const previousPeriodTotal = getPreviousPeriodTotalCost(
2296
- dailyCost.aggregation,
2297
- duration,
2298
- lastCompleteBillingDate
2299
- );
2300
- if (!notEmpty(dailyCostRatio) || !notEmpty(metricDataRatio)) {
2301
- return {
2302
- amount: previousPeriodTotal
2303
- };
2304
- }
2305
- return {
2306
- ratio: dailyCostRatio - metricDataRatio,
2307
- amount: previousPeriodTotal * (dailyCostRatio - metricDataRatio)
2308
- };
2309
- }
2310
- function getPreviousPeriodTotalCost(aggregation, duration, inclusiveEndDate) {
2311
- const luxonDuration = Duration$1.fromISO(duration);
2312
- const startDate = inclusiveStartDateOf(duration, inclusiveEndDate);
2313
- const nextPeriodStart = DateTime.fromISO(startDate).plus(luxonDuration);
2314
- return aggregation.reduce((acc, costByDate) => {
2315
- return DateTime.fromISO(costByDate.date) < nextPeriodStart ? acc + costByDate.amount : acc;
2316
- }, 0);
2317
- }
2318
- function choose([savings, excess], change) {
2319
- var _a;
2320
- const isSavings = ((_a = change.ratio) != null ? _a : change.amount) <= 0;
2321
- return isSavings ? savings : excess;
2322
- }
2323
-
2324
- const vowels = {
2325
- a: "A",
2326
- e: "E",
2327
- i: "I",
2328
- o: "O",
2329
- u: "U"
2330
- };
2331
- const indefiniteArticleOf = (articles, word) => {
2332
- const firstChar = word.charAt(0).toLocaleLowerCase("en-US");
2333
- return firstChar in vowels ? `${articles[1]} ${word}` : `${articles[0]} ${word}`;
2334
- };
2335
-
2336
- const CostGrowth = (props) => {
2337
- var _a, _b;
2338
- const { change, duration } = props;
2339
- const styles = useCostGrowthStyles();
2340
- const { engineerCost, engineerThreshold } = useConfig();
2341
- const [currency] = useCurrency();
2342
- const amount = Math.abs(change.amount);
2343
- const ratio = Math.abs((_a = change.ratio) != null ? _a : NaN);
2344
- const rate = rateOf(engineerCost, duration);
2345
- const engineers = amount / rate;
2346
- const converted = amount / ((_b = currency.rate) != null ? _b : rate);
2347
- const growth = notEmpty(change.ratio) ? growthOf({ ratio: change.ratio, amount: engineers }, engineerThreshold) : null;
2348
- const classes = classnames({
2349
- [styles.excess]: growth === GrowthType.Excess,
2350
- [styles.savings]: growth === GrowthType.Savings
2351
- });
2352
- if (engineers < engineerThreshold) {
2353
- return /* @__PURE__ */ React.createElement(Typography, { component: "span", className: classes }, "Negligible");
2354
- }
2355
- if (currency.kind === CurrencyType.USD) {
2356
- if (isNaN(ratio)) {
2357
- return /* @__PURE__ */ React.createElement(Typography, { component: "span", className: classes }, "~", currency.prefix, formatCurrency(converted));
2358
- }
2359
- return /* @__PURE__ */ React.createElement(Typography, { component: "span", className: classes }, formatPercent(ratio), " or ~", currency.prefix, formatCurrency(converted));
2360
- }
2361
- if (amount < 1) {
2362
- return /* @__PURE__ */ React.createElement(Typography, { component: "span", className: classes }, "less than ", indefiniteArticleOf(["a", "an"], currency.unit));
2363
- }
2364
- if (isNaN(ratio)) {
2365
- return /* @__PURE__ */ React.createElement(Typography, { component: "span", className: classes }, "~", formatCurrency(converted, currency.unit));
2366
- }
2367
- return /* @__PURE__ */ React.createElement(Typography, { component: "span", className: classes }, formatPercent(ratio), " or ~", formatCurrency(converted, currency.unit));
2368
- };
2369
-
2370
- const CostGrowthIndicator = (props) => {
2371
- const { engineerThreshold } = useConfig();
2372
- const { change, formatter, className, ...extraProps } = props;
2373
- const classes = useCostGrowthStyles();
2374
- const growth = growthOf(change, engineerThreshold);
2375
- const classNames = classnames(classes.indicator, className, {
2376
- [classes.excess]: growth === GrowthType.Excess,
2377
- [classes.savings]: growth === GrowthType.Savings
2378
- });
2379
- return /* @__PURE__ */ React.createElement(Typography, { className: classNames, component: "span", ...extraProps }, formatter ? formatter(change, { absolute: true }) : change.ratio, growth === GrowthType.Excess && /* @__PURE__ */ React.createElement(ArrowDropUp, { "aria-label": "excess" }), growth === GrowthType.Savings && /* @__PURE__ */ React.createElement(ArrowDropDown, { "aria-label": "savings" }));
2380
- };
2381
-
2382
- const mapFiltersToProps = ({ pageFilters, setPageFilters }) => ({
2383
- ...pageFilters,
2384
- setGroup: (group) => setPageFilters({
2385
- ...pageFilters,
2386
- group: group.id,
2387
- project: null
2388
- })
2389
- });
2390
- const mapLoadingToProps = ({ actions, dispatch }) => ({
2391
- loadingActions: actions,
2392
- dispatchReset: (loadingActions) => dispatch(getResetStateWithoutInitial(loadingActions))
2393
- });
2394
-
2395
- const CostInsightsTabs = ({ groups }) => {
2396
- const classes = useCostInsightsTabsStyles();
2397
- const [index] = useState(0);
2398
- const [groupMenuEl, setGroupMenuEl] = useState(null);
2399
- const { group, setGroup } = useFilters(mapFiltersToProps);
2400
- const { loadingActions, dispatchReset } = useLoading(mapLoadingToProps);
2401
- const openGroupMenu = (e) => setGroupMenuEl(e.currentTarget);
2402
- const closeGroupMenu = () => setGroupMenuEl(null);
2403
- const updateGroupFilterAndCloseMenu = (g) => () => {
2404
- dispatchReset(loadingActions);
2405
- closeGroupMenu();
2406
- setGroup(g);
2407
- };
2408
- const renderTabLabel = () => /* @__PURE__ */ React.createElement("div", { className: classes.tabLabel }, /* @__PURE__ */ React.createElement(Typography, { className: classes.tabLabelText, variant: "overline" }, `${groups.length} teams`), /* @__PURE__ */ React.createElement(ExpandMoreIcon, { fontSize: "small" }));
2409
- const hasAtLeastTwoGroups = groups.length >= 2;
2410
- if (!hasAtLeastTwoGroups)
2411
- return null;
2412
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
2413
- Tabs,
2414
- {
2415
- className: `cost-insights-tabs ${classes.tabs}`,
2416
- "data-testid": "cost-insights-tabs",
2417
- classes: { indicator: classes.indicator },
2418
- value: index
2419
- },
2420
- /* @__PURE__ */ React.createElement(
2421
- Tab,
2422
- {
2423
- className: classes.tab,
2424
- "data-testid": "cost-insights-groups-tab",
2425
- key: "cost-insights-groups-tab",
2426
- label: renderTabLabel(),
2427
- onClick: openGroupMenu,
2428
- component: "button"
2429
- }
2430
- )
2431
- ), /* @__PURE__ */ React.createElement(
2432
- Menu,
2433
- {
2434
- id: "group-menu",
2435
- "data-testid": "group-menu",
2436
- className: classes.menu,
2437
- getContentAnchorEl: null,
2438
- anchorEl: groupMenuEl,
2439
- keepMounted: true,
2440
- open: Boolean(groupMenuEl),
2441
- onClose: closeGroupMenu,
2442
- anchorOrigin: {
2443
- vertical: "bottom",
2444
- horizontal: "left"
2445
- },
2446
- transformOrigin: {
2447
- vertical: "top",
2448
- horizontal: "left"
2449
- }
2450
- },
2451
- groups.map((g) => {
2452
- var _a;
2453
- return /* @__PURE__ */ React.createElement(
2454
- MenuItem,
2455
- {
2456
- className: classes.menuItem,
2457
- classes: { selected: classes.menuItemSelected },
2458
- selected: g.id === group,
2459
- key: g.id,
2460
- "data-testid": g.id,
2461
- onClick: updateGroupFilterAndCloseMenu(g)
2462
- },
2463
- (_a = g.name) != null ? _a : g.id
2464
- );
2465
- })
2466
- ));
2467
- };
2468
-
2469
- const useStyles = makeStyles((theme) => ({
2470
- root: {
2471
- gridArea: "pageContent"
2472
- },
2473
- header: {
2474
- boxShadow: "none"
2475
- },
2476
- content: {
2477
- padding: theme.spacing(4)
2478
- }
2479
- }));
2480
- const CostInsightsLayout = ({
2481
- title = "Cost Insights",
2482
- groups,
2483
- children
2484
- }) => {
2485
- const classes = useStyles();
2486
- return /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(
2487
- Header,
2488
- {
2489
- style: { boxShadow: "none" },
2490
- title: "Cost Insights",
2491
- pageTitleOverride: title,
2492
- type: "Tool"
2493
- }
2494
- ), /* @__PURE__ */ React.createElement("div", { className: classes.root }, /* @__PURE__ */ React.createElement(CostInsightsTabs, { groups }), /* @__PURE__ */ React.createElement("div", { className: classes.content }, children)));
2495
- };
2496
-
2497
- const NavigationMenuItem = ({ navigation, icon, title }) => {
2498
- const classes = useNavigationStyles();
2499
- const [, setScroll] = useScroll();
2500
- return /* @__PURE__ */ React.createElement(
2501
- MenuItem,
2502
- {
2503
- "data-testid": `menu-item-${navigation}`,
2504
- className: classes.menuItem,
2505
- onClick: () => setScroll(navigation)
2506
- },
2507
- /* @__PURE__ */ React.createElement(ListItemIcon, { className: classes.listItemIcon }, icon),
2508
- /* @__PURE__ */ React.createElement(
2509
- ListItemText,
2510
- {
2511
- primary: /* @__PURE__ */ React.createElement(Typography, { className: classes.title }, title)
2512
- }
2513
- )
2514
- );
2515
- };
2516
- const CostInsightsNavigation = React.memo(
2517
- ({ alerts, products }) => {
2518
- var _a;
2519
- const classes = useNavigationStyles();
2520
- const { icons } = useConfig();
2521
- const [isOpen, setOpen] = useState(false);
2522
- const defaultNavigationItems = getDefaultNavigationItems(alerts);
2523
- const productNavigationItems = (_a = products == null ? void 0 : products.map((product) => ({
2524
- title: product.name,
2525
- navigation: product.kind,
2526
- icon: findAlways(icons, (i) => i.kind === product.kind).component
2527
- }))) != null ? _a : [];
2528
- useEffect(
2529
- function toggleProductMenuItems() {
2530
- if (products == null ? void 0 : products.length) {
2531
- setOpen(true);
2532
- } else {
2533
- setOpen(false);
2534
- }
2535
- },
2536
- [products]
2537
- );
2538
- return /* @__PURE__ */ React.createElement(MenuList, { className: classes.menuList }, defaultNavigationItems.map((item) => /* @__PURE__ */ React.createElement(
2539
- NavigationMenuItem,
2540
- {
2541
- key: `navigation-menu-item-${item.navigation}`,
2542
- navigation: item.navigation,
2543
- title: item.title,
2544
- icon: item.navigation === DefaultNavigation.AlertInsightsHeader ? /* @__PURE__ */ React.createElement(Badge, { badgeContent: alerts, color: "secondary" }, React.cloneElement(item.icon, {
2545
- className: classes.navigationIcon
2546
- })) : React.cloneElement(item.icon, {
2547
- className: classes.navigationIcon
2548
- })
2549
- }
2550
- )), /* @__PURE__ */ React.createElement(Collapse, { in: isOpen, timeout: 850 }, productNavigationItems.map((item) => /* @__PURE__ */ React.createElement(
2551
- NavigationMenuItem,
2552
- {
2553
- key: `navigation-menu-item-${item.navigation}`,
2554
- navigation: item.navigation,
2555
- icon: React.cloneElement(item.icon, {
2556
- className: classes.navigationIcon
2557
- }),
2558
- title: item.title
2559
- }
2560
- ))));
2561
- }
2562
- );
2563
-
2564
- const createRenderer = (baseCurrency) => (col, classes) => {
2565
- return function render(rowData) {
2566
- const row = rowData;
2567
- const rowStyles = classnames(classes.row, {
2568
- [classes.rowTotal]: row.id === "total",
2569
- [classes.colFirst]: col === "label",
2570
- [classes.colLast]: col === "change"
2571
- });
2572
- switch (col) {
2573
- case "previous":
2574
- case "current":
2575
- return /* @__PURE__ */ React.createElement(Typography, { className: rowStyles }, baseCurrency.format(row[col]));
2576
- case "change":
2577
- return /* @__PURE__ */ React.createElement(
2578
- CostGrowthIndicator,
2579
- {
2580
- className: rowStyles,
2581
- change: row.change,
2582
- formatter: formatChange
2583
- }
2584
- );
2585
- default:
2586
- return /* @__PURE__ */ React.createElement(Typography, { className: rowStyles }, row.label);
2587
- }
2588
- };
2589
- };
2590
- function createSorter(field) {
2591
- return function rowSort(data1, data2) {
2592
- const a = data1;
2593
- const b = data2;
2594
- if (a.id === "total")
2595
- return 1;
2596
- if (b.id === "total")
2597
- return 1;
2598
- if (field === "label")
2599
- return a.label.localeCompare(b.label);
2600
- if (field === "change") {
2601
- if (formatChange(a[field]) === "\u221E" || formatChange(b[field]) === "-\u221E")
2602
- return 1;
2603
- if (formatChange(a[field]) === "-\u221E" || formatChange(b[field]) === "\u221E")
2604
- return -1;
2605
- return a[field].ratio - b[field].ratio;
2606
- }
2607
- return b.previous + b.current - (a.previous + a.current);
2608
- };
2609
- }
2610
- const ProductEntityTable = ({
2611
- entityLabel,
2612
- entity,
2613
- options
2614
- }) => {
2615
- const classes = useEntityDialogStyles();
2616
- const { baseCurrency } = useConfig();
2617
- const entities = entity.entities[entityLabel];
2618
- const data = Object.assign(
2619
- {
2620
- previousName: "Previous",
2621
- currentName: "Current"
2622
- },
2623
- options
2624
- );
2625
- const firstColClasses = classnames(classes.column, classes.colFirst);
2626
- const lastColClasses = classnames(classes.column, classes.colLast);
2627
- const columns = [
2628
- {
2629
- field: "label",
2630
- title: /* @__PURE__ */ React.createElement(Typography, { className: firstColClasses }, entityLabel),
2631
- render: createRenderer(baseCurrency)("label", classes),
2632
- customSort: createSorter("label"),
2633
- width: "33.33%"
2634
- },
2635
- {
2636
- field: "previous",
2637
- title: /* @__PURE__ */ React.createElement(Typography, { className: classes.column }, data.previousName),
2638
- align: "right",
2639
- render: createRenderer(baseCurrency)("previous", classes),
2640
- customSort: createSorter("previous")
2641
- },
2642
- {
2643
- field: "current",
2644
- title: /* @__PURE__ */ React.createElement(Typography, { className: classes.column }, data.currentName),
2645
- align: "right",
2646
- render: createRenderer(baseCurrency)("current", classes),
2647
- customSort: createSorter("current")
2648
- },
2649
- {
2650
- field: "change",
2651
- title: /* @__PURE__ */ React.createElement(Typography, { className: lastColClasses }, "Change"),
2652
- align: "right",
2653
- render: createRenderer(baseCurrency)("change", classes),
2654
- customSort: createSorter("change")
2655
- }
2656
- ];
2657
- const rowData = entities.map((e) => ({
2658
- id: e.id || "Unknown",
2659
- label: e.id || "Unknown",
2660
- previous: e.aggregation[0],
2661
- current: e.aggregation[1],
2662
- change: e.change
2663
- })).concat({
2664
- id: "total",
2665
- label: "Total",
2666
- previous: entity.aggregation[0],
2667
- current: entity.aggregation[1],
2668
- change: entity.change
2669
- }).sort(createSorter());
2670
- return /* @__PURE__ */ React.createElement(
2671
- Table,
2672
- {
2673
- columns,
2674
- data: rowData,
2675
- title: entity.id || "Unlabeled",
2676
- options: {
2677
- paging: false,
2678
- search: false,
2679
- hideFilterIcons: true
2680
- }
2681
- }
2682
- );
2683
- };
2684
-
2685
- const ProductEntityDialog = ({
2686
- open,
2687
- entity,
2688
- options = {},
2689
- onClose
2690
- }) => {
2691
- const classes = useEntityDialogStyles();
2692
- const labels = Object.keys(entity.entities);
2693
- const [selectedLabel, setSelectedLabel] = useState(
2694
- findAlways(labels, (_) => true)
2695
- );
2696
- const tabs = labels.map((label, index) => ({
2697
- id: index.toString(),
2698
- label: `Breakdown by ${label}`
2699
- }));
2700
- return /* @__PURE__ */ React.createElement(Dialog, { open, onClose, scroll: "body", fullWidth: true, maxWidth: "lg" }, /* @__PURE__ */ React.createElement(IconButton, { className: classes.closeButton, onClick: onClose }, /* @__PURE__ */ React.createElement(CloseIcon, null)), /* @__PURE__ */ React.createElement(
2701
- HeaderTabs,
2702
- {
2703
- tabs,
2704
- onChange: (index) => setSelectedLabel(labels[index])
2705
- }
2706
- ), /* @__PURE__ */ React.createElement(
2707
- ProductEntityTable,
2708
- {
2709
- entityLabel: selectedLabel,
2710
- entity,
2711
- options
2712
- }
2713
- ));
2714
- };
2715
-
2716
- const ProductInsightsChart = ({
2717
- billingDate,
2718
- entity,
2719
- duration
2720
- }) => {
2721
- const classes = useProductInsightsChartStyles();
2722
- const layoutClasses = useBarChartLayoutStyles();
2723
- const { baseCurrency } = useConfig();
2724
- const entities = useMemo(() => {
2725
- var _a;
2726
- const entityLabel = assertAlways(findAnyKey(entity.entities));
2727
- return (_a = entity.entities[entityLabel]) != null ? _a : [];
2728
- }, [entity]);
2729
- const [activeLabel, setActive] = useState();
2730
- const [selectLabel, setSelected] = useState();
2731
- const isSelected = useMemo(() => !isUndefined(selectLabel), [selectLabel]);
2732
- const isClickable = useMemo(() => {
2733
- var _a, _b;
2734
- const breakdowns = Object.keys(
2735
- (_b = (_a = entities.find((e) => e.id === activeLabel)) == null ? void 0 : _a.entities) != null ? _b : {}
2736
- );
2737
- return breakdowns.length > 0;
2738
- }, [entities, activeLabel]);
2739
- const costStart = entity.aggregation[0];
2740
- const costEnd = entity.aggregation[1];
2741
- const resources = entities.map(resourceOf);
2742
- const options = {
2743
- previousName: formatPeriod(duration, billingDate, false),
2744
- currentName: formatPeriod(duration, billingDate, true)
2745
- };
2746
- const onMouseMove = (data) => {
2747
- if (isLabeled(data)) {
2748
- setActive(data.activeLabel);
2749
- } else if (isUnlabeled(data)) {
2750
- setActive(null);
2751
- } else {
2752
- setActive(void 0);
2753
- }
2754
- };
2755
- const onClick = (data) => {
2756
- if (isLabeled(data)) {
2757
- setSelected(data.activeLabel);
2758
- } else if (isUnlabeled(data)) {
2759
- setSelected(null);
2760
- } else {
2761
- setSelected(void 0);
2762
- }
2763
- };
2764
- const renderProductInsightsTooltip = ({
2765
- label,
2766
- payload = []
2767
- }) => {
2768
- if (isInvalid({ label, payload }))
2769
- return null;
2770
- const id = label === "" ? null : label;
2771
- const title = titleOf(label);
2772
- const items = payload.map(tooltipItemOf(baseCurrency)).filter(notEmpty);
2773
- const activeEntity = findAlways(entities, (e) => e.id === id);
2774
- const breakdowns = Object.keys(activeEntity.entities);
2775
- if (breakdowns.length) {
2776
- const subtitle = breakdowns.map((b) => pluralize(b, activeEntity.entities[b].length, true)).join(", ");
2777
- return /* @__PURE__ */ React.createElement(
2778
- BarChartTooltip,
2779
- {
2780
- title,
2781
- subtitle,
2782
- topRight: !!activeEntity.change.ratio && /* @__PURE__ */ React.createElement(
2783
- CostGrowthIndicator,
2784
- {
2785
- formatter: formatChange,
2786
- change: activeEntity.change,
2787
- className: classes.indicator
2788
- }
2789
- ),
2790
- actions: /* @__PURE__ */ React.createElement(Box, { className: classes.actions }, /* @__PURE__ */ React.createElement(FullScreenIcon, null), /* @__PURE__ */ React.createElement(Typography, null, "Click for breakdown"))
2791
- },
2792
- items.map((item, index) => /* @__PURE__ */ React.createElement(BarChartTooltipItem, { key: `${item.label}-${index}`, item }))
2793
- );
2794
- }
2795
- return /* @__PURE__ */ React.createElement(
2796
- BarChartTooltip,
2797
- {
2798
- title,
2799
- topRight: !!activeEntity.change.ratio && /* @__PURE__ */ React.createElement(
2800
- CostGrowthIndicator,
2801
- {
2802
- formatter: formatChange,
2803
- change: activeEntity.change,
2804
- className: classes.indicator
2805
- }
2806
- ),
2807
- content: id ? null : "This product has costs that are not labeled and therefore can't be attributed to a specific entity."
2808
- },
2809
- items.map((item, index) => /* @__PURE__ */ React.createElement(BarChartTooltipItem, { key: `${item.label}-${index}`, item }))
2810
- );
2811
- };
2812
- const barChartProps = isClickable ? { onClick } : {};
2813
- return /* @__PURE__ */ React.createElement(Box, { className: layoutClasses.wrapper }, /* @__PURE__ */ React.createElement(BarChartLegend, { costStart, costEnd, options }, /* @__PURE__ */ React.createElement(
2814
- LegendItem,
2815
- {
2816
- title: choose(["Cost Savings", "Cost Excess"], entity.change)
2817
- },
2818
- /* @__PURE__ */ React.createElement(CostGrowth, { change: entity.change, duration })
2819
- )), /* @__PURE__ */ React.createElement(
2820
- BarChart,
2821
- {
2822
- resources,
2823
- tooltip: renderProductInsightsTooltip,
2824
- onMouseMove,
2825
- options,
2826
- ...barChartProps
2827
- }
2828
- ), isSelected && entities.length && /* @__PURE__ */ React.createElement(
2829
- ProductEntityDialog,
2830
- {
2831
- open: isSelected,
2832
- onClose: () => setSelected(void 0),
2833
- entity: findAlways(entities, (e) => e.id === selectLabel),
2834
- options
2835
- }
2836
- ));
2837
- };
2838
-
2839
- const ProjectGrowthAlertChart = ({
2840
- alert
2841
- }) => {
2842
- const classes = useBarChartLayoutStyles();
2843
- const costStart = alert.aggregation[0];
2844
- const costEnd = alert.aggregation[1];
2845
- const resourceData = alert.products.map(resourceOf);
2846
- const options = {
2847
- previousName: DateTime.fromFormat(alert.periodStart, "yyyy-'Q'q").toFormat(
2848
- "'Q'q yyyy"
2849
- ),
2850
- currentName: DateTime.fromFormat(alert.periodEnd, "yyyy-'Q'q").toFormat(
2851
- "'Q'q yyyy"
2852
- )
2853
- };
2854
- 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 }));
2855
- };
2856
-
2857
- const ProjectGrowthAlertCard = ({ alert }) => {
2858
- const subheader = `
2859
- ${pluralize("product", alert.products.length, true)}${alert.products.length > 1 ? ", sorted by cost" : ""}`;
2860
- return /* @__PURE__ */ React.createElement(
2861
- InfoCard,
2862
- {
2863
- title: `Project growth for ${alert.project}`,
2864
- subheader
2865
- },
2866
- /* @__PURE__ */ React.createElement(ProjectGrowthAlertChart, { alert })
2867
- );
2868
- };
2869
-
2870
- var __defProp$2 = Object.defineProperty;
2871
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
2872
- var __publicField$2 = (obj, key, value) => {
2873
- __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
2874
- return value;
2875
- };
2876
- class ProjectGrowthAlert {
2877
- constructor(data) {
2878
- __publicField$2(this, "data");
2879
- this.data = data;
2880
- }
2881
- get url() {
2882
- return "/cost-insights/investigating-growth";
2883
- }
2884
- get title() {
2885
- return `Investigate cost growth in project ${this.data.project}`;
2886
- }
2887
- get subtitle() {
2888
- return "Cost growth outpacing business growth is unsustainable long-term.";
2889
- }
2890
- get element() {
2891
- return /* @__PURE__ */ React.createElement(ProjectGrowthAlertCard, { alert: this.data });
2892
- }
2893
- }
2894
-
2895
- const UnlabeledDataflowAlertCard = ({
2896
- alert
2897
- }) => {
2898
- const classes = useBarChartLayoutStyles();
2899
- const projects = pluralize("project", alert.projects.length, true);
2900
- const subheader = `
2901
- Showing costs from ${projects} with unlabeled Dataflow jobs in the last 30 days.
2902
- `;
2903
- const options = {
2904
- previousName: "Unlabeled Cost",
2905
- currentName: "Labeled Cost"
2906
- };
2907
- const resources = alert.projects.map((project) => ({
2908
- name: project.id,
2909
- previous: project.unlabeledCost,
2910
- current: project.labeledCost
2911
- }));
2912
- return /* @__PURE__ */ React.createElement(InfoCard, { title: "Label Dataflow", subheader }, /* @__PURE__ */ React.createElement(Box, { className: classes.wrapper }, /* @__PURE__ */ React.createElement(
2913
- BarChartLegend,
2914
- {
2915
- costStart: alert.unlabeledCost,
2916
- costEnd: alert.labeledCost,
2917
- options
2918
- }
2919
- ), /* @__PURE__ */ React.createElement(BarChart, { resources, options })));
2920
- };
2921
-
2922
- var __defProp$1 = Object.defineProperty;
2923
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
2924
- var __publicField$1 = (obj, key, value) => {
2925
- __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
2926
- return value;
2927
- };
2928
- class UnlabeledDataflowAlert {
2929
- constructor(data) {
2930
- __publicField$1(this, "data");
2931
- __publicField$1(this, "status");
2932
- this.data = data;
2933
- }
2934
- get url() {
2935
- return "/cost-insights/labeling-jobs";
2936
- }
2937
- get title() {
2938
- return "Add labels to workflows";
2939
- }
2940
- get subtitle() {
2941
- return "Labels show in billing data, enabling cost insights for each workflow.";
2942
- }
2943
- get element() {
2944
- return /* @__PURE__ */ React.createElement(UnlabeledDataflowAlertCard, { alert: this.data });
2945
- }
2946
- }
2947
-
2948
- const today = DateTime.now().toFormat(DEFAULT_DATE_FORMAT);
2949
- const ProjectGrowthInstructionsPage = () => {
2950
- const alertData = {
2951
- project: "example-project",
2952
- periodStart: "Q1 2020",
2953
- periodEnd: "Q2 2020",
2954
- aggregation: [6e4, 12e4],
2955
- change: {
2956
- ratio: 1,
2957
- amount: 6e4
2958
- },
2959
- products: [
2960
- {
2961
- id: "Compute Engine",
2962
- aggregation: [58e3, 118e3]
2963
- },
2964
- {
2965
- id: "Cloud Dataflow",
2966
- aggregation: [1200, 1500]
2967
- },
2968
- {
2969
- id: "Cloud Storage",
2970
- aggregation: [800, 500]
2971
- }
2972
- ]
2973
- };
2974
- const projectGrowthAlert = new ProjectGrowthAlert(alertData);
2975
- const product = {
2976
- kind: "ComputeEngine",
2977
- name: "Compute Engine"
2978
- };
2979
- const entity = {
2980
- id: "example-id",
2981
- aggregation: [2e4, 6e4],
2982
- change: {
2983
- ratio: 3,
2984
- amount: 4e4
2985
- },
2986
- entities: {
2987
- service: [
2988
- {
2989
- id: "service-one",
2990
- aggregation: [18200, 58500],
2991
- entities: {},
2992
- change: { ratio: 2.21, amount: 40300 }
2993
- },
2994
- {
2995
- id: "service-two",
2996
- aggregation: [1200, 1300],
2997
- entities: {},
2998
- change: { ratio: 0.083, amount: 100 }
2999
- },
3000
- {
3001
- id: "service-three",
3002
- aggregation: [600, 200],
3003
- entities: {},
3004
- change: { ratio: -0.666, amount: -400 }
3005
- }
3006
- ]
3007
- }
3008
- };
3009
- 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(
3010
- ProductInsightsChart,
3011
- {
3012
- billingDate: today,
3013
- duration: Duration.P3M,
3014
- entity
3015
- }
3016
- ))), /* @__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.")));
3017
- };
3018
-
3019
- const KubernetesMigrationBarChartLegend = ({
3020
- currentProduct,
3021
- comparedProduct,
3022
- change,
3023
- startDate
3024
- }) => {
3025
- const theme = useTheme();
3026
- return /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "row" }, /* @__PURE__ */ React.createElement(Box, { marginRight: 2 }, /* @__PURE__ */ React.createElement(
3027
- LegendItem,
3028
- {
3029
- title: monthOf(startDate),
3030
- markerColor: theme.palette.magenta
3031
- },
3032
- currentProduct
3033
- )), /* @__PURE__ */ React.createElement(Box, { marginRight: 2 }, /* @__PURE__ */ React.createElement(LegendItem, { title: "Estimated Cost", markerColor: theme.palette.yellow }, comparedProduct)), /* @__PURE__ */ React.createElement(LegendItem, { title: "Total Savings" }, /* @__PURE__ */ React.createElement(CostGrowth, { change, duration: Duration.P30D })));
3034
- };
3035
-
3036
- const KubernetesMigrationBarChart = ({
3037
- currentProduct,
3038
- comparedProduct,
3039
- services
3040
- }) => {
3041
- const theme = useTheme();
3042
- const options = {
3043
- previousFill: theme.palette.magenta,
3044
- currentFill: theme.palette.yellow,
3045
- previousName: comparedProduct,
3046
- currentName: currentProduct
3047
- };
3048
- const resources = services.map((service) => ({
3049
- name: service.id,
3050
- previous: service.aggregation[0],
3051
- current: service.aggregation[1]
3052
- }));
3053
- return /* @__PURE__ */ React.createElement(BarChart, { resources, options });
3054
- };
3055
-
3056
- const KubernetesMigrationAlertCard = ({
3057
- data,
3058
- title,
3059
- subheader,
3060
- currentProduct,
3061
- comparedProduct
3062
- }) => {
3063
- return /* @__PURE__ */ React.createElement(InfoCard, { title, subheader }, /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { paddingY: 1 }, /* @__PURE__ */ React.createElement(
3064
- KubernetesMigrationBarChartLegend,
3065
- {
3066
- startDate: data.startDate,
3067
- change: data.change,
3068
- currentProduct,
3069
- comparedProduct
3070
- }
3071
- )), /* @__PURE__ */ React.createElement(Box, { paddingY: 1 }, /* @__PURE__ */ React.createElement(
3072
- KubernetesMigrationBarChart,
3073
- {
3074
- services: data.services,
3075
- currentProduct,
3076
- comparedProduct
3077
- }
3078
- ))));
3079
- };
3080
-
3081
- const KubernetesMigrationDismissForm = forwardRef(({ onSubmit, disableSubmit, alert }, ref) => {
3082
- const [services, setServices] = useState(alert.data.services);
3083
- const onFormSubmit = (e) => {
3084
- e.preventDefault();
3085
- onSubmit({ services });
3086
- };
3087
- const onCheckboxChange = (e, checked) => {
3088
- if (checked) {
3089
- const service = findAlways(
3090
- alert.data.services,
3091
- (s) => s.id === e.target.value
3092
- );
3093
- setServices((prevServices) => prevServices.concat(service));
3094
- } else {
3095
- setServices(
3096
- (prevServices) => prevServices.filter((p) => p.id !== e.target.value)
3097
- );
3098
- }
3099
- };
3100
- useEffect(() => {
3101
- if (services.length) {
3102
- disableSubmit(false);
3103
- } else {
3104
- disableSubmit(true);
3105
- }
3106
- }, [services, disableSubmit]);
3107
- return (
3108
- /* All custom forms must accept a ref and implement an onSubmit handler. */
3109
- /* @__PURE__ */ React.createElement("form", { ref, onSubmit: onFormSubmit }, /* @__PURE__ */ React.createElement(FormControl, { component: "fieldset", fullWidth: true }, /* @__PURE__ */ React.createElement(Typography, { color: "textPrimary" }, /* @__PURE__ */ React.createElement("b", null, "Or choose which services to dismiss this alert for.")), /* @__PURE__ */ React.createElement(FormGroup, null, alert.data.services.map((service, index) => /* @__PURE__ */ React.createElement(
3110
- FormControlLabel,
3111
- {
3112
- key: `example-option-${index}`,
3113
- label: service.id,
3114
- value: service.id,
3115
- control: /* @__PURE__ */ React.createElement(
3116
- Checkbox,
3117
- {
3118
- color: "primary",
3119
- checked: services.some((p) => p.id === service.id),
3120
- onChange: onCheckboxChange
3121
- }
3122
- )
3123
- }
3124
- )))))
3125
- );
3126
- });
3127
-
3128
- var __defProp = Object.defineProperty;
3129
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3130
- var __publicField = (obj, key, value) => {
3131
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
3132
- return value;
3133
- };
3134
- class KubernetesMigrationAlert {
3135
- constructor(api, data) {
3136
- __publicField(this, "api");
3137
- __publicField(this, "data");
3138
- __publicField(this, "subtitle", "Services running on Kubernetes are estimated to save 50% or more compared to Compute Engine.");
3139
- // Dialog will not render a form if form property set to null.
3140
- __publicField(this, "AcceptForm", null);
3141
- // Overrides default Dismiss form with a custom form component.
3142
- __publicField(this, "DismissForm", KubernetesMigrationDismissForm);
3143
- this.api = api;
3144
- this.data = data;
3145
- }
3146
- get title() {
3147
- return /* @__PURE__ */ React.createElement(Typography, { component: "span" }, "Consider migrating", " ", pluralize("service", this.data.services.length, true), " to Kubernetes", " ", /* @__PURE__ */ React.createElement(Lifecycle, { shorthand: true }));
3148
- }
3149
- get element() {
3150
- const subheader = `${pluralize(
3151
- "Service",
3152
- this.data.services.length,
3153
- true
3154
- )}, sorted by cost`;
3155
- return /* @__PURE__ */ React.createElement(
3156
- KubernetesMigrationAlertCard,
3157
- {
3158
- data: this.data,
3159
- title: "Migrate to Kubernetes",
3160
- subheader,
3161
- currentProduct: "Compute Engine",
3162
- comparedProduct: "Kubernetes"
3163
- }
3164
- );
3165
- }
3166
- /* Fires when the onSubmit event is raised on a Dismiss form. Displays custom dismiss form. */
3167
- async onDismissed(options) {
3168
- const alerts = await this.api.getAlerts(options.group);
3169
- return new Promise(
3170
- (resolve) => setTimeout(resolve, 750, [
3171
- ...alerts.filter((a) => a.title !== this.title),
3172
- {
3173
- title: this.title,
3174
- subtitle: this.subtitle,
3175
- status: AlertStatus.Dismissed
3176
- }
3177
- ])
3178
- );
3179
- }
3180
- /* Fires when the onSubmit event is raised on a Snooze form. Displays default snooze form. */
3181
- async onSnoozed(options) {
3182
- const alerts = await this.api.getAlerts(options.group);
3183
- return new Promise(
3184
- (resolve) => setTimeout(resolve, 750, [
3185
- ...alerts.filter((a) => a.title !== this.title),
3186
- {
3187
- title: this.title,
3188
- subtitle: this.subtitle,
3189
- status: AlertStatus.Snoozed
3190
- }
3191
- ])
3192
- );
3193
- }
3194
- /* Fires when the Accept button is clicked. Dialog does not render a form. See KubernetesMigrationAlert.AcceptForm */
3195
- async onAccepted(options) {
3196
- const alerts = await this.api.getAlerts(options.group);
3197
- return new Promise(
3198
- (resolve) => setTimeout(resolve, 750, [
3199
- ...alerts.filter((a) => a.title !== this.title),
3200
- {
3201
- title: this.title,
3202
- subtitle: this.subtitle,
3203
- status: AlertStatus.Accepted
3204
- }
3205
- ])
3206
- );
3207
- }
3208
- }
3209
-
3210
- const createMockProduct = (callback) => {
3211
- const defaultProduct = {
3212
- kind: "compute-engine",
3213
- name: "Compute Engine"
3214
- };
3215
- if (typeof callback === "function") {
3216
- return callback({ ...defaultProduct });
3217
- }
3218
- return { ...defaultProduct };
3219
- };
3220
- const MockProductTypes = {
3221
- "compute-engine": "Compute Engine",
3222
- "cloud-dataflow": "Cloud Dataflow",
3223
- "cloud-storage": "Cloud Storage",
3224
- "big-query": "Big Query",
3225
- "big-table": "BigTable",
3226
- "cloud-pub-sub": "Cloud Pub/Sub"
3227
- };
3228
- const MockProducts = Object.keys(MockProductTypes).map(
3229
- (productType) => createMockProduct(() => ({
3230
- kind: productType,
3231
- name: MockProductTypes[productType]
3232
- }))
3233
- );
3234
- findAlways(
3235
- MockProducts,
3236
- (p) => p.kind === "compute-engine"
3237
- );
3238
- findAlways(
3239
- MockProducts,
3240
- (p) => p.kind === "cloud-dataflow"
3241
- );
3242
- findAlways(
3243
- MockProducts,
3244
- (p) => p.kind === "cloud-storage"
3245
- );
3246
- findAlways(
3247
- MockProducts,
3248
- (p) => p.kind === "big-query"
3249
- );
3250
- findAlways(
3251
- MockProducts,
3252
- (p) => p.kind === "big-table"
3253
- );
3254
-
3255
- Object.keys(
3256
- MockProductTypes
3257
- ).map((productType) => ({ duration: Duration.P30D, productType }));
3258
-
3259
- const MockDefaultLoadingActions = [
3260
- DefaultLoadingAction.UserGroups,
3261
- DefaultLoadingAction.CostInsightsInitial,
3262
- DefaultLoadingAction.CostInsightsPage
3263
- ].concat(MockProducts.map((product) => product.kind));
3264
- getDefaultState(
3265
- MockDefaultLoadingActions
3266
- );
3267
-
3268
- const MockBigQueryInsights = {
3269
- id: "bigQuery",
3270
- aggregation: [1e4, 3e4],
3271
- change: {
3272
- ratio: 3,
3273
- amount: 2e4
3274
- },
3275
- entities: {
3276
- dataset: [
3277
- {
3278
- id: "dataset-a",
3279
- aggregation: [5e3, 1e4],
3280
- change: {
3281
- ratio: 1,
3282
- amount: 5e3
3283
- },
3284
- entities: {}
3285
- },
3286
- {
3287
- id: "dataset-b",
3288
- aggregation: [5e3, 1e4],
3289
- change: {
3290
- ratio: 1,
3291
- amount: 5e3
3292
- },
3293
- entities: {}
3294
- },
3295
- {
3296
- id: "dataset-c",
3297
- aggregation: [0, 1e4],
3298
- change: {
3299
- amount: 1e4
3300
- },
3301
- entities: {}
3302
- }
3303
- ]
3304
- }
3305
- };
3306
- const MockCloudDataflowInsights = {
3307
- id: "cloudDataflow",
3308
- aggregation: [1e5, 158e3],
3309
- change: {
3310
- ratio: 0.58,
3311
- amount: 58e3
3312
- },
3313
- entities: {
3314
- pipeline: [
3315
- {
3316
- id: null,
3317
- aggregation: [1e4, 12e3],
3318
- change: {
3319
- ratio: 0.2,
3320
- amount: 2e3
3321
- },
3322
- entities: {
3323
- SKU: [
3324
- {
3325
- id: "Mock SKU A",
3326
- aggregation: [3e3, 4e3],
3327
- change: {
3328
- ratio: 0.333333,
3329
- amount: 1e3
3330
- },
3331
- entities: {}
3332
- },
3333
- {
3334
- id: "Mock SKU B",
3335
- aggregation: [7e3, 8e3],
3336
- change: {
3337
- ratio: 0.14285714,
3338
- amount: 1e3
3339
- },
3340
- entities: {}
3341
- }
3342
- ]
3343
- }
3344
- },
3345
- {
3346
- id: "pipeline-a",
3347
- aggregation: [6e4, 7e4],
3348
- change: {
3349
- ratio: 0.16666666666666666,
3350
- amount: 1e4
3351
- },
3352
- entities: {
3353
- SKU: [
3354
- {
3355
- id: "Mock SKU A",
3356
- aggregation: [2e4, 15e3],
3357
- change: {
3358
- ratio: -0.25,
3359
- amount: -5e3
3360
- },
3361
- entities: {}
3362
- },
3363
- {
3364
- id: "Mock SKU B",
3365
- aggregation: [3e4, 35e3],
3366
- change: {
3367
- ratio: -0.16666666666666666,
3368
- amount: -5e3
3369
- },
3370
- entities: {}
3371
- },
3372
- {
3373
- id: "Mock SKU C",
3374
- aggregation: [1e4, 2e4],
3375
- change: {
3376
- ratio: 1,
3377
- amount: 1e4
3378
- },
3379
- entities: {}
3380
- }
3381
- ]
3382
- }
3383
- },
3384
- {
3385
- id: "pipeline-b",
3386
- aggregation: [12e3, 8e3],
3387
- change: {
3388
- ratio: -0.33333,
3389
- amount: -4e3
3390
- },
3391
- entities: {
3392
- SKU: [
3393
- {
3394
- id: "Mock SKU A",
3395
- aggregation: [4e3, 4e3],
3396
- change: {
3397
- ratio: 0,
3398
- amount: 0
3399
- },
3400
- entities: {}
3401
- },
3402
- {
3403
- id: "Mock SKU B",
3404
- aggregation: [8e3, 4e3],
3405
- change: {
3406
- ratio: -0.5,
3407
- amount: -4e3
3408
- },
3409
- entities: {}
3410
- }
3411
- ]
3412
- }
3413
- },
3414
- {
3415
- id: "pipeline-c",
3416
- aggregation: [0, 1e4],
3417
- change: {
3418
- amount: 1e4
3419
- },
3420
- entities: {}
3421
- }
3422
- ]
3423
- }
3424
- };
3425
- const MockCloudStorageInsights = {
3426
- id: "cloudStorage",
3427
- aggregation: [45e3, 45e3],
3428
- change: {
3429
- ratio: 0,
3430
- amount: 0
3431
- },
3432
- entities: {
3433
- bucket: [
3434
- {
3435
- id: "bucket-a",
3436
- aggregation: [15e3, 2e4],
3437
- change: {
3438
- ratio: 0.333,
3439
- amount: 5e3
3440
- },
3441
- entities: {
3442
- SKU: [
3443
- {
3444
- id: "Mock SKU A",
3445
- aggregation: [1e4, 11e3],
3446
- change: {
3447
- ratio: 0.1,
3448
- amount: 1e3
3449
- },
3450
- entities: {}
3451
- },
3452
- {
3453
- id: "Mock SKU B",
3454
- aggregation: [2e3, 5e3],
3455
- change: {
3456
- ratio: 1.5,
3457
- amount: 3e3
3458
- },
3459
- entities: {}
3460
- },
3461
- {
3462
- id: "Mock SKU C",
3463
- aggregation: [3e3, 4e3],
3464
- change: {
3465
- ratio: 0.3333,
3466
- amount: 1e3
3467
- },
3468
- entities: {}
3469
- }
3470
- ]
3471
- }
3472
- },
3473
- {
3474
- id: "bucket-b",
3475
- aggregation: [3e4, 25e3],
3476
- change: {
3477
- ratio: -0.16666,
3478
- amount: -5e3
3479
- },
3480
- entities: {
3481
- SKU: [
3482
- {
3483
- id: "Mock SKU A",
3484
- aggregation: [12e3, 13e3],
3485
- change: {
3486
- ratio: 0.08333333333333333,
3487
- amount: 1e3
3488
- },
3489
- entities: {}
3490
- },
3491
- {
3492
- id: "Mock SKU B",
3493
- aggregation: [16e3, 12e3],
3494
- change: {
3495
- ratio: -0.25,
3496
- amount: -4e3
3497
- },
3498
- entities: {}
3499
- },
3500
- {
3501
- id: "Mock SKU C",
3502
- aggregation: [2e3, 0],
3503
- change: {
3504
- amount: -2e3
3505
- },
3506
- entities: {}
3507
- }
3508
- ]
3509
- }
3510
- },
3511
- {
3512
- id: "bucket-c",
3513
- aggregation: [0, 0],
3514
- change: {
3515
- amount: 0
3516
- },
3517
- entities: {}
3518
- }
3519
- ]
3520
- }
3521
- };
3522
- const MockComputeEngineInsights = {
3523
- id: "computeEngine",
3524
- aggregation: [8e4, 9e4],
3525
- change: {
3526
- ratio: 0.125,
3527
- amount: 1e4
3528
- },
3529
- entities: {
3530
- service: [
3531
- {
3532
- id: "service-a",
3533
- aggregation: [2e4, 1e4],
3534
- change: {
3535
- ratio: -0.5,
3536
- amount: -1e4
3537
- },
3538
- entities: {
3539
- SKU: [
3540
- {
3541
- id: "Mock SKU A",
3542
- aggregation: [4e3, 2e3],
3543
- change: {
3544
- ratio: -0.5,
3545
- amount: -2e3
3546
- },
3547
- entities: {}
3548
- },
3549
- {
3550
- id: "Mock SKU B",
3551
- aggregation: [7e3, 6e3],
3552
- change: {
3553
- ratio: -0.14285714285714285,
3554
- amount: -1e3
3555
- },
3556
- entities: {}
3557
- },
3558
- {
3559
- id: "Mock SKU C",
3560
- aggregation: [9e3, 2e3],
3561
- change: {
3562
- ratio: -0.7777777777777778,
3563
- amount: -7e3
3564
- },
3565
- entities: {}
3566
- }
3567
- ],
3568
- deployment: [
3569
- {
3570
- id: "Compute Engine",
3571
- aggregation: [7e3, 6e3],
3572
- change: {
3573
- ratio: -0.5,
3574
- amount: -2e3
3575
- },
3576
- entities: {}
3577
- },
3578
- {
3579
- id: "Kubernetes",
3580
- aggregation: [4e3, 2e3],
3581
- change: {
3582
- ratio: -0.14285714285714285,
3583
- amount: -1e3
3584
- },
3585
- entities: {}
3586
- }
3587
- ]
3588
- }
3589
- },
3590
- {
3591
- id: "service-b",
3592
- aggregation: [1e4, 2e4],
3593
- change: {
3594
- ratio: 1,
3595
- amount: 1e4
3596
- },
3597
- entities: {
3598
- SKU: [
3599
- {
3600
- id: "Mock SKU A",
3601
- aggregation: [1e3, 2e3],
3602
- change: {
3603
- ratio: 1,
3604
- amount: 1e3
3605
- },
3606
- entities: {}
3607
- },
3608
- {
3609
- id: "Mock SKU B",
3610
- aggregation: [4e3, 8e3],
3611
- change: {
3612
- ratio: 1,
3613
- amount: 4e3
3614
- },
3615
- entities: {}
3616
- },
3617
- {
3618
- id: "Mock SKU C",
3619
- aggregation: [5e3, 1e4],
3620
- change: {
3621
- ratio: 1,
3622
- amount: 5e3
3623
- },
3624
- entities: {}
3625
- }
3626
- ],
3627
- deployment: [
3628
- {
3629
- id: "Compute Engine",
3630
- aggregation: [7e3, 6e3],
3631
- change: {
3632
- ratio: -0.5,
3633
- amount: -2e3
3634
- },
3635
- entities: {}
3636
- },
3637
- {
3638
- id: "Kubernetes",
3639
- aggregation: [4e3, 2e3],
3640
- change: {
3641
- ratio: -0.14285714285714285,
3642
- amount: -1e3
3643
- },
3644
- entities: {}
3645
- }
3646
- ]
3647
- }
3648
- },
3649
- {
3650
- id: "service-c",
3651
- aggregation: [0, 1e4],
3652
- change: {
3653
- amount: 1e4
3654
- },
3655
- entities: {}
3656
- }
3657
- ]
3658
- }
3659
- };
3660
- const MockEventsInsights = {
3661
- id: "events",
3662
- aggregation: [2e4, 1e4],
3663
- change: {
3664
- ratio: -0.5,
3665
- amount: -1e4
3666
- },
3667
- entities: {
3668
- event: [
3669
- {
3670
- id: "event-a",
3671
- aggregation: [15e3, 7e3],
3672
- change: {
3673
- ratio: -0.53333333333,
3674
- amount: -8e3
3675
- },
3676
- entities: {
3677
- product: [
3678
- {
3679
- id: "Mock Product A",
3680
- aggregation: [5e3, 2e3],
3681
- change: {
3682
- ratio: -0.6,
3683
- amount: -3e3
3684
- },
3685
- entities: {}
3686
- },
3687
- {
3688
- id: "Mock Product B",
3689
- aggregation: [7e3, 2500],
3690
- change: {
3691
- ratio: -0.64285714285,
3692
- amount: -4500
3693
- },
3694
- entities: {}
3695
- },
3696
- {
3697
- id: "Mock Product C",
3698
- aggregation: [3e3, 2500],
3699
- change: {
3700
- ratio: -0.16666666666,
3701
- amount: -500
3702
- },
3703
- entities: {}
3704
- }
3705
- ]
3706
- }
3707
- },
3708
- {
3709
- id: "event-b",
3710
- aggregation: [5e3, 3e3],
3711
- change: {
3712
- ratio: -0.4,
3713
- amount: -2e3
3714
- },
3715
- entities: {
3716
- product: [
3717
- {
3718
- id: "Mock Product A",
3719
- aggregation: [2e3, 1e3],
3720
- change: {
3721
- ratio: -0.5,
3722
- amount: -1e3
3723
- },
3724
- entities: {}
3725
- },
3726
- {
3727
- id: "Mock Product B",
3728
- aggregation: [1e3, 1500],
3729
- change: {
3730
- ratio: 0.5,
3731
- amount: 500
3732
- },
3733
- entities: {}
3734
- },
3735
- {
3736
- id: "Mock Product C",
3737
- aggregation: [2e3, 500],
3738
- change: {
3739
- ratio: -0.75,
3740
- amount: -1500
3741
- },
3742
- entities: {}
3743
- }
3744
- ]
3745
- }
3746
- }
3747
- ]
3748
- }
3749
- };
3750
-
3751
- const MockConfigProvider = (props) => {
3752
- const { children, ...context } = props;
3753
- const defaultContext = {
3754
- baseCurrency: createCurrencyFormat(),
3755
- metrics: [],
3756
- products: [],
3757
- icons: [],
3758
- engineerCost: 0,
3759
- engineerThreshold: EngineerThreshold,
3760
- hideTrendLine: false,
3761
- currencies: []
3762
- };
3763
- return /* @__PURE__ */ React.createElement(ConfigContext.Provider, { value: { ...defaultContext, ...context } }, children);
3764
- };
3765
- const MockCurrencyProvider = (props) => {
3766
- const { children, ...context } = props;
3767
- const defaultContext = {
3768
- currency: {
3769
- kind: null,
3770
- label: "Engineers \u{1F6E0}",
3771
- unit: "engineer"
3772
- },
3773
- setCurrency: jest.fn()
3774
- };
3775
- return /* @__PURE__ */ React.createElement(CurrencyContext.Provider, { value: { ...defaultContext, ...context } }, children);
3776
- };
3777
-
3778
- function parseIntervals(intervals) {
3779
- const match = intervals.match(
3780
- /\/(?<duration>P\d+[DM])\/(?<date>\d{4}-\d{2}-\d{2})/
3781
- );
3782
- if (Object.keys((match == null ? void 0 : match.groups) || {}).length !== 2) {
3783
- throw new Error(`Invalid intervals: ${intervals}`);
3784
- }
3785
- const { duration, date } = match.groups;
3786
- return {
3787
- duration,
3788
- endDate: date
3789
- };
3790
- }
3791
- function aggregationFor(intervals, baseline) {
3792
- const { duration, endDate } = parseIntervals(intervals);
3793
- const inclusiveEndDate = inclusiveEndDateOf(duration, endDate);
3794
- const days = DateTime.fromISO(endDate).diff(
3795
- DateTime.fromISO(inclusiveStartDateOf(duration, inclusiveEndDate)),
3796
- "days"
3797
- ).days;
3798
- function nextDelta() {
3799
- const varianceFromBaseline = 0.15;
3800
- const positiveTrendChance = 0.55;
3801
- const normalization = positiveTrendChance - 1;
3802
- return baseline * (Math.random() + normalization) * varianceFromBaseline;
3803
- }
3804
- return [...Array(days).keys()].reduce(
3805
- (values, i) => {
3806
- const last = values.length ? values[values.length - 1].amount : baseline;
3807
- const date = DateTime.fromISO(
3808
- inclusiveStartDateOf(duration, inclusiveEndDate)
3809
- ).plus({ days: i }).toFormat(DEFAULT_DATE_FORMAT);
3810
- const amount = Math.max(0, last + nextDelta());
3811
- values.push({
3812
- date,
3813
- amount
3814
- });
3815
- return values;
3816
- },
3817
- []
3818
- );
3819
- }
3820
- function changeOf(aggregation) {
3821
- const firstAmount = aggregation.length ? aggregation[0].amount : 0;
3822
- const lastAmount = aggregation.length ? aggregation[aggregation.length - 1].amount : 0;
3823
- if (!firstAmount || !lastAmount) {
3824
- return {
3825
- amount: lastAmount - firstAmount
3826
- };
3827
- }
3828
- return {
3829
- ratio: (lastAmount - firstAmount) / firstAmount,
3830
- amount: lastAmount - firstAmount
3831
- };
3832
- }
3833
- function trendlineOf(aggregation) {
3834
- const data = aggregation.map((a) => [
3835
- Date.parse(a.date) / 1e3,
3836
- a.amount
3837
- ]);
3838
- const result = regression.linear(data, { precision: 5 });
3839
- return {
3840
- slope: result.equation[0],
3841
- intercept: result.equation[1]
3842
- };
3843
- }
3844
- function entityOf(product) {
3845
- switch (product) {
3846
- case "computeEngine":
3847
- return MockComputeEngineInsights;
3848
- case "cloudDataflow":
3849
- return MockCloudDataflowInsights;
3850
- case "cloudStorage":
3851
- return MockCloudStorageInsights;
3852
- case "bigQuery":
3853
- return MockBigQueryInsights;
3854
- case "events":
3855
- return MockEventsInsights;
3856
- default:
3857
- throw new Error(
3858
- `Cannot get insights for ${product}. Make sure product matches product property in app-info.yaml`
3859
- );
3860
- }
3861
- }
3862
- const getGroupedProducts = (intervals) => [
3863
- {
3864
- id: "Cloud Dataflow",
3865
- aggregation: aggregationFor(intervals, 1700)
3866
- },
3867
- {
3868
- id: "Compute Engine",
3869
- aggregation: aggregationFor(intervals, 350)
3870
- },
3871
- {
3872
- id: "Cloud Storage",
3873
- aggregation: aggregationFor(intervals, 1300)
3874
- },
3875
- {
3876
- id: "BigQuery",
3877
- aggregation: aggregationFor(intervals, 2e3)
3878
- },
3879
- {
3880
- id: "Cloud SQL",
3881
- aggregation: aggregationFor(intervals, 750)
3882
- },
3883
- {
3884
- id: "Cloud Spanner",
3885
- aggregation: aggregationFor(intervals, 50)
3886
- },
3887
- {
3888
- id: "Cloud Pub/Sub",
3889
- aggregation: aggregationFor(intervals, 1e3)
3890
- },
3891
- {
3892
- id: "Cloud Bigtable",
3893
- aggregation: aggregationFor(intervals, 250)
3894
- }
3895
- ];
3896
- const getGroupedProjects = (intervals) => [
3897
- {
3898
- id: "project-a",
3899
- aggregation: aggregationFor(intervals, 1700)
3900
- },
3901
- {
3902
- id: "project-b",
3903
- aggregation: aggregationFor(intervals, 350)
3904
- },
3905
- {
3906
- id: "project-c",
3907
- aggregation: aggregationFor(intervals, 1300)
3908
- }
3909
- ];
3910
-
3911
- class ExampleCostInsightsClient {
3912
- request(_, res) {
3913
- return new Promise((resolve) => setTimeout(resolve, 0, res));
3914
- }
3915
- getLastCompleteBillingDate() {
3916
- return Promise.resolve(
3917
- DateTime.now().minus({ days: 1 }).toFormat(DEFAULT_DATE_FORMAT)
3918
- );
3919
- }
3920
- async getUserGroups(userId) {
3921
- const groups = await this.request({ userId }, [
3922
- { id: "group-a", name: "Group A" },
3923
- { id: "group-b", name: "Group B" }
3924
- ]);
3925
- return groups;
3926
- }
3927
- async getGroupProjects(group) {
3928
- const projects = await this.request({ group }, [
3929
- { id: "project-a" },
3930
- { id: "project-b" },
3931
- { id: "project-c" }
3932
- ]);
3933
- return projects;
3934
- }
3935
- async getDailyMetricData(metric, intervals) {
3936
- const aggregation = aggregationFor(intervals, 1e5).map((entry) => ({
3937
- ...entry,
3938
- amount: Math.round(entry.amount)
3939
- }));
3940
- const cost = await this.request(
3941
- { metric, intervals },
3942
- {
3943
- format: "number",
3944
- aggregation,
3945
- change: changeOf(aggregation),
3946
- trendline: trendlineOf(aggregation)
3947
- }
3948
- );
3949
- return cost;
3950
- }
3951
- async getCatalogEntityDailyCost(entityRef, intervals) {
3952
- const aggregation = aggregationFor(intervals, 8e3);
3953
- const groupDailyCost = await this.request(
3954
- { entityRef, intervals },
3955
- {
3956
- aggregation,
3957
- change: changeOf(aggregation),
3958
- trendline: trendlineOf(aggregation),
3959
- // Optional field providing cost groupings / breakdowns keyed by the type. In this example,
3960
- // daily cost grouped by cloud product OR by project / billing account.
3961
- groupedCosts: {
3962
- product: getGroupedProducts(intervals),
3963
- project: getGroupedProjects(intervals)
3964
- }
3965
- }
3966
- );
3967
- return groupDailyCost;
3968
- }
3969
- async getGroupDailyCost(group, intervals) {
3970
- const aggregation = aggregationFor(intervals, 8e3);
3971
- const groupDailyCost = await this.request(
3972
- { group, intervals },
3973
- {
3974
- aggregation,
3975
- change: changeOf(aggregation),
3976
- trendline: trendlineOf(aggregation),
3977
- // Optional field providing cost groupings / breakdowns keyed by the type. In this example,
3978
- // daily cost grouped by cloud product OR by project / billing account.
3979
- groupedCosts: {
3980
- product: getGroupedProducts(intervals),
3981
- project: getGroupedProjects(intervals)
3982
- }
3983
- }
3984
- );
3985
- return groupDailyCost;
3986
- }
3987
- async getProjectDailyCost(project, intervals) {
3988
- const aggregation = aggregationFor(intervals, 1500);
3989
- const projectDailyCost = await this.request(
3990
- { project, intervals },
3991
- {
3992
- id: "project-a",
3993
- aggregation,
3994
- change: changeOf(aggregation),
3995
- trendline: trendlineOf(aggregation),
3996
- // Optional field providing cost groupings / breakdowns keyed by the type. In this example,
3997
- // daily project cost grouped by cloud product.
3998
- groupedCosts: {
3999
- product: getGroupedProducts(intervals)
4000
- }
4001
- }
4002
- );
4003
- return projectDailyCost;
4004
- }
4005
- async getProductInsights(options) {
4006
- const productInsights = await this.request(
4007
- options,
4008
- entityOf(options.product)
4009
- );
4010
- return productInsights;
4011
- }
4012
- async getAlerts(group) {
4013
- const projectGrowthData = {
4014
- project: "example-project",
4015
- periodStart: "2020-Q2",
4016
- periodEnd: "2020-Q3",
4017
- aggregation: [6e4, 12e4],
4018
- change: {
4019
- ratio: 1,
4020
- amount: 6e4
4021
- },
4022
- products: [
4023
- { id: "Compute Engine", aggregation: [58e3, 118e3] },
4024
- { id: "Cloud Dataflow", aggregation: [1200, 1500] },
4025
- { id: "Cloud Storage", aggregation: [800, 500] }
4026
- ]
4027
- };
4028
- const unlabeledDataflowData = {
4029
- periodStart: "2020-09-01",
4030
- periodEnd: "2020-09-30",
4031
- labeledCost: 6200,
4032
- unlabeledCost: 7e3,
4033
- projects: [
4034
- {
4035
- id: "example-project-1",
4036
- unlabeledCost: 5e3,
4037
- labeledCost: 3e3
4038
- },
4039
- {
4040
- id: "example-project-2",
4041
- unlabeledCost: 2e3,
4042
- labeledCost: 3200
4043
- }
4044
- ]
4045
- };
4046
- const today = DateTime.now();
4047
- const alerts = await this.request({ group }, [
4048
- new ProjectGrowthAlert(projectGrowthData),
4049
- new UnlabeledDataflowAlert(unlabeledDataflowData),
4050
- new KubernetesMigrationAlert(this, {
4051
- startDate: today.minus({ days: 30 }).toFormat(DEFAULT_DATE_FORMAT),
4052
- endDate: today.toFormat(DEFAULT_DATE_FORMAT),
4053
- change: {
4054
- ratio: 0,
4055
- amount: 0
4056
- },
4057
- services: [
4058
- {
4059
- id: "service-a",
4060
- aggregation: [2e4, 1e4],
4061
- change: {
4062
- ratio: -0.5,
4063
- amount: -1e4
4064
- },
4065
- entities: {}
4066
- },
4067
- {
4068
- id: "service-b",
4069
- aggregation: [3e4, 15e3],
4070
- change: {
4071
- ratio: -0.5,
4072
- amount: -15e3
4073
- },
4074
- entities: {}
4075
- }
4076
- ]
4077
- })
4078
- ]);
4079
- return alerts;
4080
- }
4081
- }
4082
-
4083
- export { isDismissEnabled as $, AlertInstructionsLayout as A, BillingDateProvider as B, CostInsightsThemeProvider as C, DEFAULT_DATE_FORMAT as D, formatLastTwoLookaheadQuarters as E, FilterProvider as F, GroupsProvider as G, findAlways as H, useOverviewTabsStyles as I, DefaultNavigation as J, DefaultLoadingAction as K, LoadingProvider as L, getResetState as M, getResetStateWithoutInitial as N, useActionItemCardStyles as O, ProjectGrowthInstructionsPage as P, AlertStatus as Q, ScrollType as R, ScrollProvider as S, useAlertDialogStyles as T, choose$1 as U, formOf as V, useAlertStatusSummaryButtonStyles as W, useCostInsightsStyles as X, useAlertInsightsSectionStyles as Y, isSnoozeEnabled as Z, isAcceptEnabled as _, ConfigProvider as a, isStatusSnoozed as a0, isStatusAccepted as a1, isStatusDismissed as a2, sumOfAllAlerts as a3, useProductInsightsCardStyles as a4, findAnyKey as a5, ProductInsightsChart as a6, DEFAULT_DURATION as a7, settledResponseOf as a8, initialStatesOf as a9, EngineerThreshold as aA, ChangeThreshold as aB, GrowthType as aC, CurrencyType as aD, IconType as aE, totalAggregationSort as aa, useSubtleTypographyStyles as ab, useGroups as ac, useCurrency as ad, isAlertActive as ae, isAlertSnoozed as af, isAlertAccepted as ag, isAlertDismissed as ah, CostInsightsLayout as ai, CostInsightsNavigation as aj, costInsightsPlugin as ak, CostInsightsPage as al, EntityCostInsightsContent as am, CostInsightsProjectGrowthInstructionsPage as an, CostInsightsLabelDataflowInstructionsPage as ao, ExampleCostInsightsClient as ap, BarChart as aq, CostGrowthIndicator as ar, MockConfigProvider as as, MockCurrencyProvider as at, ProjectGrowthAlert as au, UnlabeledDataflowAlert as av, AlertDismissReason as aw, AlertDismissOptions as ax, AlertSnoozeOptions as ay, DataKey as az, CurrencyProvider as b, costInsightsApiRef as c, useLastCompleteBillingDate as d, useFilters as e, useScroll as f, getComparedChange as g, LegendItem as h, intervalsOf as i, formatChange as j, choose as k, CostGrowth as l, useCostOverviewStyles as m, useConfig as n, aggregationSort as o, overviewGraphTickFormatter as p, isInvalid as q, BarChartTooltip as r, BarChartTooltipItem as s, formatGraphValue as t, useLoading as u, getPreviousPeriodTotalCost as v, formatPeriod as w, BarChartLegend as x, useSelectStyles as y, Duration as z };
4084
- //# sourceMappingURL=index-BFM2mP6Z.esm.js.map