@backstage-community/plugin-cost-insights 0.12.25 → 0.12.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/alerts/ProjectGrowthAlert.esm.js +24 -0
- package/dist/alerts/ProjectGrowthAlert.esm.js.map +1 -0
- package/dist/alerts/UnlabeledDataflowAlert.esm.js +25 -0
- package/dist/alerts/UnlabeledDataflowAlert.esm.js.map +1 -0
- package/dist/api/CostInsightsApi.esm.js +8 -0
- package/dist/api/CostInsightsApi.esm.js.map +1 -0
- package/dist/components/ActionItems/ActionItemCard.esm.js +46 -0
- package/dist/components/ActionItems/ActionItemCard.esm.js.map +1 -0
- package/dist/components/ActionItems/ActionItems.esm.js +90 -0
- package/dist/components/ActionItems/ActionItems.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertDialog.esm.js +123 -0
- package/dist/components/AlertInsights/AlertDialog.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertInsights.esm.js +157 -0
- package/dist/components/AlertInsights/AlertInsights.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertInsightsHeader.esm.js +17 -0
- package/dist/components/AlertInsights/AlertInsightsHeader.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertInsightsSection.esm.js +57 -0
- package/dist/components/AlertInsights/AlertInsightsSection.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertInsightsSectionHeader.esm.js +30 -0
- package/dist/components/AlertInsights/AlertInsightsSectionHeader.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertStatusSummary.esm.js +93 -0
- package/dist/components/AlertInsights/AlertStatusSummary.esm.js.map +1 -0
- package/dist/components/AlertInsights/AlertStatusSummaryButton.esm.js +35 -0
- package/dist/components/AlertInsights/AlertStatusSummaryButton.esm.js.map +1 -0
- package/dist/components/AlertInstructionsLayout/AlertInstructionsLayout.esm.js +47 -0
- package/dist/components/AlertInstructionsLayout/AlertInstructionsLayout.esm.js.map +1 -0
- package/dist/components/BarChart/BarChart.esm.js +167 -0
- package/dist/components/BarChart/BarChart.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartLabel.esm.js +31 -0
- package/dist/components/BarChart/BarChartLabel.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartLegend.esm.js +47 -0
- package/dist/components/BarChart/BarChartLegend.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartStepper.esm.js +91 -0
- package/dist/components/BarChart/BarChartStepper.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartStepperButton.esm.js +27 -0
- package/dist/components/BarChart/BarChartStepperButton.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartSteps.esm.js +25 -0
- package/dist/components/BarChart/BarChartSteps.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartTick.esm.js +29 -0
- package/dist/components/BarChart/BarChartTick.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartTooltip.esm.js +30 -0
- package/dist/components/BarChart/BarChartTooltip.esm.js.map +1 -0
- package/dist/components/BarChart/BarChartTooltipItem.esm.js +24 -0
- package/dist/components/BarChart/BarChartTooltipItem.esm.js.map +1 -0
- package/dist/components/CopyUrlToClipboard/CopyUrlToClipboard.esm.js +44 -0
- package/dist/components/CopyUrlToClipboard/CopyUrlToClipboard.esm.js.map +1 -0
- package/dist/components/CostGrowth/CostGrowth.esm.js +55 -0
- package/dist/components/CostGrowth/CostGrowth.esm.js.map +1 -0
- package/dist/components/CostGrowth/CostGrowthIndicator.esm.js +31 -0
- package/dist/components/CostGrowth/CostGrowthIndicator.esm.js.map +1 -0
- package/dist/components/CostInsightsHeader/CostInsightsHeader.esm.js +57 -0
- package/dist/components/CostInsightsHeader/CostInsightsHeader.esm.js.map +1 -0
- package/dist/components/CostInsightsLayout/CostInsightsLayout.esm.js +35 -0
- package/dist/components/CostInsightsLayout/CostInsightsLayout.esm.js.map +1 -0
- package/dist/components/CostInsightsNavigation/CostInsightsNavigation.esm.js +87 -0
- package/dist/components/CostInsightsNavigation/CostInsightsNavigation.esm.js.map +1 -0
- package/dist/components/CostInsightsPage/CostInsightsPage.esm.js +229 -0
- package/dist/components/CostInsightsPage/CostInsightsPage.esm.js.map +1 -0
- package/dist/components/CostInsightsPage/CostInsightsPageRoot.esm.js +15 -0
- package/dist/components/CostInsightsPage/CostInsightsPageRoot.esm.js.map +1 -0
- package/dist/components/CostInsightsPage/CostInsightsThemeProvider.esm.js +24 -0
- package/dist/components/CostInsightsPage/CostInsightsThemeProvider.esm.js.map +1 -0
- package/dist/components/CostInsightsPage/index.esm.js +2 -0
- package/dist/components/CostInsightsPage/index.esm.js.map +1 -0
- package/dist/components/CostInsightsPage/selector.esm.js +15 -0
- package/dist/components/CostInsightsPage/selector.esm.js.map +1 -0
- package/dist/components/CostInsightsTabs/CostInsightsTabs.esm.js +89 -0
- package/dist/components/CostInsightsTabs/CostInsightsTabs.esm.js.map +1 -0
- package/dist/components/CostInsightsTabs/selector.esm.js +17 -0
- package/dist/components/CostInsightsTabs/selector.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js +196 -0
- package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/CostOverviewCard.esm.js +99 -0
- package/dist/components/CostOverviewCard/CostOverviewCard.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/CostOverviewChart.esm.js +157 -0
- package/dist/components/CostOverviewCard/CostOverviewChart.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/CostOverviewHeader.esm.js +25 -0
- package/dist/components/CostOverviewCard/CostOverviewHeader.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js +54 -0
- package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js.map +1 -0
- package/dist/components/CostOverviewCard/selector.esm.js +22 -0
- package/dist/components/CostOverviewCard/selector.esm.js.map +1 -0
- package/dist/components/CurrencySelect/CurrencySelect.esm.js +52 -0
- package/dist/components/CurrencySelect/CurrencySelect.esm.js.map +1 -0
- package/dist/{esm/index-BPzWHt3A.esm.js → components/EntityCosts/EntityCosts.esm.js} +14 -80
- package/dist/{esm/index-BPzWHt3A.esm.js.map → components/EntityCosts/EntityCosts.esm.js.map} +1 -1
- package/dist/components/EntityCosts/index.esm.js +2 -0
- package/dist/components/EntityCosts/index.esm.js.map +1 -0
- package/dist/{esm/index-BA6m_qFS.esm.js → components/LabelDataflowInstructionsPage/LabelDataflowInstructionsPage.esm.js} +2 -79
- package/dist/components/LabelDataflowInstructionsPage/LabelDataflowInstructionsPage.esm.js.map +1 -0
- package/dist/components/LabelDataflowInstructionsPage/index.esm.js +2 -0
- package/dist/components/LabelDataflowInstructionsPage/index.esm.js.map +1 -0
- package/dist/components/LegendItem/LegendItem.esm.js +44 -0
- package/dist/components/LegendItem/LegendItem.esm.js.map +1 -0
- package/dist/components/MetricSelect/MetricSelect.esm.js +37 -0
- package/dist/components/MetricSelect/MetricSelect.esm.js.map +1 -0
- package/dist/components/PeriodSelect/PeriodSelect.esm.js +72 -0
- package/dist/components/PeriodSelect/PeriodSelect.esm.js.map +1 -0
- package/dist/components/ProductInsights/ProductInsights.esm.js +98 -0
- package/dist/components/ProductInsights/ProductInsights.esm.js.map +1 -0
- package/dist/components/ProductInsightsCard/ProductEntityDialog.esm.js +42 -0
- package/dist/components/ProductInsightsCard/ProductEntityDialog.esm.js.map +1 -0
- package/dist/components/ProductInsightsCard/ProductEntityTable.esm.js +139 -0
- package/dist/components/ProductInsightsCard/ProductEntityTable.esm.js.map +1 -0
- package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js +83 -0
- package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js.map +1 -0
- package/dist/components/ProductInsightsCard/ProductInsightsCardList.esm.js +34 -0
- package/dist/components/ProductInsightsCard/ProductInsightsCardList.esm.js.map +1 -0
- package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js +148 -0
- package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js.map +1 -0
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.esm.js +20 -0
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.esm.js.map +1 -0
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.esm.js +46 -0
- package/dist/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.esm.js.map +1 -0
- package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js +111 -0
- package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js.map +1 -0
- package/dist/components/ProjectGrowthInstructionsPage/index.esm.js +2 -0
- package/dist/components/ProjectGrowthInstructionsPage/index.esm.js.map +1 -0
- package/dist/components/ProjectSelect/ProjectSelect.esm.js +47 -0
- package/dist/components/ProjectSelect/ProjectSelect.esm.js.map +1 -0
- package/dist/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.esm.js +41 -0
- package/dist/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.esm.js.map +1 -0
- package/dist/components/WhyCostsMatter/WhyCostsMatter.esm.js +21 -0
- package/dist/components/WhyCostsMatter/WhyCostsMatter.esm.js.map +1 -0
- package/dist/example/alerts/KubernetesMigrationAlert.esm.js +86 -0
- package/dist/example/alerts/KubernetesMigrationAlert.esm.js.map +1 -0
- package/dist/example/client.esm.js +194 -0
- package/dist/example/client.esm.js.map +1 -0
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.esm.js +33 -0
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationAlertCard.esm.js.map +1 -0
- package/dist/{esm/index-BTIHr4n_.esm.js → example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js} +69 -48
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js.map +1 -0
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js +107 -0
- package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js.map +1 -0
- package/dist/example/forms/KubernetesMigrationDismissForm.esm.js +57 -0
- package/dist/example/forms/KubernetesMigrationDismissForm.esm.js.map +1 -0
- package/dist/forms/AlertAcceptForm.esm.js +27 -0
- package/dist/forms/AlertAcceptForm.esm.js.map +1 -0
- package/dist/forms/AlertDismissForm.esm.js +100 -0
- package/dist/forms/AlertDismissForm.esm.js.map +1 -0
- package/dist/forms/AlertSnoozeForm.esm.js +52 -0
- package/dist/forms/AlertSnoozeForm.esm.js.map +1 -0
- package/dist/hooks/useConfig.esm.js +153 -0
- package/dist/hooks/useConfig.esm.js.map +1 -0
- package/dist/hooks/useCurrency.esm.js +26 -0
- package/dist/hooks/useCurrency.esm.js.map +1 -0
- package/dist/hooks/useFilters.esm.js +67 -0
- package/dist/hooks/useFilters.esm.js.map +1 -0
- package/dist/hooks/useGroups.esm.js +55 -0
- package/dist/hooks/useGroups.esm.js.map +1 -0
- package/dist/hooks/useLastCompleteBillingDate.esm.js +65 -0
- package/dist/hooks/useLastCompleteBillingDate.esm.js.map +1 -0
- package/dist/hooks/useLoading.esm.js +54 -0
- package/dist/hooks/useLoading.esm.js.map +1 -0
- package/dist/hooks/useScroll.esm.js +24 -0
- package/dist/hooks/useScroll.esm.js.map +1 -0
- package/dist/index.esm.js +37 -22
- package/dist/index.esm.js.map +1 -1
- package/dist/plugin.esm.js +55 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/testUtils/filters.esm.js +8 -0
- package/dist/testUtils/filters.esm.js.map +1 -0
- package/dist/testUtils/loading.esm.js +14 -0
- package/dist/testUtils/loading.esm.js.map +1 -0
- package/dist/testUtils/mockData.esm.js +485 -0
- package/dist/testUtils/mockData.esm.js.map +1 -0
- package/dist/testUtils/products.esm.js +49 -0
- package/dist/testUtils/products.esm.js.map +1 -0
- package/dist/testUtils/providers.esm.js +41 -0
- package/dist/testUtils/providers.esm.js.map +1 -0
- package/dist/testUtils/testUtils.esm.js +142 -0
- package/dist/testUtils/testUtils.esm.js.map +1 -0
- package/dist/types/Alert.esm.js +66 -0
- package/dist/types/Alert.esm.js.map +1 -0
- package/dist/types/ChangeStatistic.esm.js +15 -0
- package/dist/types/ChangeStatistic.esm.js.map +1 -0
- package/dist/types/CurrencyType.esm.js +10 -0
- package/dist/types/CurrencyType.esm.js.map +1 -0
- package/dist/types/DateFormat.esm.js +4 -0
- package/dist/types/DateFormat.esm.js.map +1 -0
- package/dist/types/Duration.esm.js +10 -0
- package/dist/types/Duration.esm.js.map +1 -0
- package/dist/types/Icon.esm.js +12 -0
- package/dist/types/Icon.esm.js.map +1 -0
- package/dist/utils/alerts.esm.js +72 -0
- package/dist/utils/alerts.esm.js.map +1 -0
- package/dist/utils/assert.esm.js +29 -0
- package/dist/utils/assert.esm.js.map +1 -0
- package/dist/utils/change.esm.js +58 -0
- package/dist/utils/change.esm.js.map +1 -0
- package/dist/utils/charts.esm.js +9 -0
- package/dist/utils/charts.esm.js.map +1 -0
- package/dist/utils/config.esm.js +19 -0
- package/dist/utils/config.esm.js.map +1 -0
- package/dist/utils/currency.esm.js +56 -0
- package/dist/utils/currency.esm.js.map +1 -0
- package/dist/utils/duration.esm.js +55 -0
- package/dist/utils/duration.esm.js.map +1 -0
- package/dist/utils/filters.esm.js +14 -0
- package/dist/utils/filters.esm.js.map +1 -0
- package/dist/utils/formatters.esm.js +103 -0
- package/dist/utils/formatters.esm.js.map +1 -0
- package/dist/utils/grammar.esm.js +14 -0
- package/dist/utils/grammar.esm.js.map +1 -0
- package/dist/utils/graphs.esm.js +48 -0
- package/dist/utils/graphs.esm.js.map +1 -0
- package/dist/utils/history.esm.js +32 -0
- package/dist/utils/history.esm.js.map +1 -0
- package/dist/utils/loading.esm.js +51 -0
- package/dist/utils/loading.esm.js.map +1 -0
- package/dist/utils/navigation.esm.js +56 -0
- package/dist/utils/navigation.esm.js.map +1 -0
- package/dist/utils/scroll.esm.js +38 -0
- package/dist/utils/scroll.esm.js.map +1 -0
- package/dist/utils/sort.esm.js +10 -0
- package/dist/utils/sort.esm.js.map +1 -0
- package/dist/utils/styles.esm.js +562 -0
- package/dist/utils/styles.esm.js.map +1 -0
- package/dist/utils/sum.esm.js +4 -0
- package/dist/utils/sum.esm.js.map +1 -0
- package/package.json +17 -12
- package/dist/esm/index-BA6m_qFS.esm.js.map +0 -1
- package/dist/esm/index-BFM2mP6Z.esm.js +0 -4084
- package/dist/esm/index-BFM2mP6Z.esm.js.map +0 -1
- package/dist/esm/index-BTIHr4n_.esm.js.map +0 -1
- package/dist/esm/index-DaitkcPN.esm.js +0 -1136
- package/dist/esm/index-DaitkcPN.esm.js.map +0 -1
- package/dist/esm/selector-D7iXJwd9.esm.js +0 -594
- package/dist/esm/selector-D7iXJwd9.esm.js.map +0 -1
|
@@ -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
|