@backstage-community/plugin-cost-insights 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/alpha/plugin.esm.js +1 -0
  3. package/dist/alpha/plugin.esm.js.map +1 -1
  4. package/dist/alpha/router.esm.js +1 -0
  5. package/dist/alpha/router.esm.js.map +1 -1
  6. package/dist/components/CostGrowth/CostGrowth.esm.js +11 -2
  7. package/dist/components/CostGrowth/CostGrowth.esm.js.map +1 -1
  8. package/dist/components/CostInsightsPage/CostInsightsPage.esm.js +5 -1
  9. package/dist/components/CostInsightsPage/CostInsightsPage.esm.js.map +1 -1
  10. package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js +20 -6
  11. package/dist/components/CostOverviewCard/CostOverviewBreakdownChart.esm.js.map +1 -1
  12. package/dist/components/CostOverviewCard/CostOverviewCard.esm.js +17 -4
  13. package/dist/components/CostOverviewCard/CostOverviewCard.esm.js.map +1 -1
  14. package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js +11 -3
  15. package/dist/components/CostOverviewCard/CostOverviewLegend.esm.js.map +1 -1
  16. package/dist/components/CostOverviewCard/selector.esm.js +3 -2
  17. package/dist/components/CostOverviewCard/selector.esm.js.map +1 -1
  18. package/dist/components/EntityCosts/EntityCosts.esm.js +5 -1
  19. package/dist/components/EntityCosts/EntityCosts.esm.js.map +1 -1
  20. package/dist/components/PeriodSelect/DateRangePicker.esm.js +218 -0
  21. package/dist/components/PeriodSelect/DateRangePicker.esm.js.map +1 -0
  22. package/dist/components/ProductInsights/ProductInsights.esm.js +9 -2
  23. package/dist/components/ProductInsights/ProductInsights.esm.js.map +1 -1
  24. package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js +32 -8
  25. package/dist/components/ProductInsightsCard/ProductInsightsCard.esm.js.map +1 -1
  26. package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js +24 -4
  27. package/dist/components/ProductInsightsCard/ProductInsightsChart.esm.js.map +1 -1
  28. package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js +1 -0
  29. package/dist/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.esm.js.map +1 -1
  30. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js +1 -0
  31. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChart.esm.js.map +1 -1
  32. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js +1 -0
  33. package/dist/example/components/KubernetesMigrationAlertCard/KubernetesMigrationBarChartLegend.esm.js.map +1 -1
  34. package/dist/index.d.ts +16 -1
  35. package/dist/index.esm.js +1 -0
  36. package/dist/index.esm.js.map +1 -1
  37. package/dist/testUtils/testUtils.esm.js +30 -11
  38. package/dist/testUtils/testUtils.esm.js.map +1 -1
  39. package/dist/types/Duration.esm.js +1 -0
  40. package/dist/types/Duration.esm.js.map +1 -1
  41. package/dist/utils/change.esm.js +20 -6
  42. package/dist/utils/change.esm.js.map +1 -1
  43. package/dist/utils/currency.esm.js +6 -1
  44. package/dist/utils/currency.esm.js.map +1 -1
  45. package/dist/utils/duration.esm.js +41 -5
  46. package/dist/utils/duration.esm.js.map +1 -1
  47. package/dist/utils/formatters.esm.js +25 -4
  48. package/dist/utils/formatters.esm.js.map +1 -1
  49. package/package.json +12 -10
  50. package/dist/components/PeriodSelect/PeriodSelect.esm.js +0 -72
  51. package/dist/components/PeriodSelect/PeriodSelect.esm.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"EntityCosts.esm.js","sources":["../../../src/components/EntityCosts/EntityCosts.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { CostOverviewCard } from '../CostOverviewCard';\nimport {\n BillingDateProvider,\n ConfigProvider,\n CurrencyProvider,\n FilterProvider,\n GroupsProvider,\n LoadingProvider,\n ScrollProvider,\n useFilters,\n useLastCompleteBillingDate,\n useLoading,\n} from '../../hooks';\nimport { CostInsightsThemeProvider } from '../CostInsightsPage/CostInsightsThemeProvider';\nimport { Progress, WarningPanel } from '@backstage/core-components';\nimport { default as MaterialAlert } from '@material-ui/lab/Alert';\nimport { mapLoadingToProps } from '../CostInsightsPage/selector';\nimport { intervalsOf } from '../../utils/duration';\nimport { costInsightsApiRef } from '../../api';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { Cost, Maybe } from '@backstage-community/plugin-cost-insights-common';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\n\nexport const EntityCostsCard = () => {\n const client = useApi(costInsightsApiRef);\n const { entity } = useEntity();\n\n const {\n loadingActions,\n loadingGroups,\n loadingBillingDate,\n loadingInitial,\n dispatchInitial,\n dispatchInsights,\n dispatchNone,\n } = useLoading(mapLoadingToProps);\n\n /* eslint-disable react-hooks/exhaustive-deps */\n // The dispatchLoading functions are derived from loading state using mapLoadingToProps, to\n // provide nicer props for the component. These are re-derived whenever loading state changes,\n // which causes an infinite loop as product panels load and re-trigger the useEffect below.\n // Since the functions don't change, we can memoize - but we trigger the same loop if we satisfy\n // exhaustive-deps by including the function itself in dependencies.\n\n const dispatchLoadingInitial = useCallback(dispatchInitial, []);\n const dispatchLoadingInsights = useCallback(dispatchInsights, []);\n const dispatchLoadingNone = useCallback(dispatchNone, []);\n /* eslint-enable react-hooks/exhaustive-deps */\n\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const [dailyCost, setDailyCost] = useState<Maybe<Cost>>(null);\n const [error, setError] = useState<Maybe<Error>>(null);\n const { pageFilters } = useFilters(p => p);\n\n useEffect(() => {\n async function getInsights() {\n setError(null);\n try {\n dispatchLoadingInsights(true);\n const intervals = intervalsOf(\n pageFilters.duration,\n lastCompleteBillingDate,\n );\n\n const fetchedDailyCost = await client.getCatalogEntityDailyCost!(\n stringifyEntityRef(entity),\n intervals,\n );\n setDailyCost(fetchedDailyCost);\n } catch (e) {\n setError(e);\n dispatchLoadingNone(loadingActions);\n } finally {\n dispatchLoadingNone(loadingActions);\n dispatchLoadingInitial(false);\n dispatchLoadingInsights(false);\n }\n }\n\n // Wait for metadata to finish loading\n if (!loadingBillingDate) {\n getInsights();\n }\n }, [\n client,\n entity,\n pageFilters,\n loadingActions,\n loadingGroups,\n loadingBillingDate,\n dispatchLoadingInsights,\n dispatchLoadingInitial,\n dispatchLoadingNone,\n lastCompleteBillingDate,\n ]);\n\n if (loadingInitial) {\n return <Progress />;\n }\n\n if (error) {\n return <MaterialAlert severity=\"error\">{error.message}</MaterialAlert>;\n }\n\n if (!dailyCost || !dailyCost.aggregation.length) {\n return <MaterialAlert severity=\"error\">No daily costs</MaterialAlert>;\n }\n\n return <CostOverviewCard dailyCostData={dailyCost} metricData={null} />;\n};\n\nexport const EntityCosts = () => {\n const client = useApi(costInsightsApiRef);\n\n if (!client.getCatalogEntityDailyCost) {\n return (\n <WarningPanel\n title=\"Could display costs for entity\"\n message=\"The getCatalogEntityDailyCost() method is not implemented by the costInsightsApi.\"\n />\n );\n }\n\n return (\n <CostInsightsThemeProvider>\n <ConfigProvider>\n <LoadingProvider>\n <GroupsProvider>\n <BillingDateProvider>\n <FilterProvider>\n <ScrollProvider>\n <CurrencyProvider>\n <EntityCostsCard />\n </CurrencyProvider>\n </ScrollProvider>\n </FilterProvider>\n </BillingDateProvider>\n </GroupsProvider>\n </LoadingProvider>\n </ConfigProvider>\n </CostInsightsThemeProvider>\n );\n};\n"],"names":["MaterialAlert"],"mappings":"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"}
1
+ {"version":3,"file":"EntityCosts.esm.js","sources":["../../../src/components/EntityCosts/EntityCosts.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { CostOverviewCard } from '../CostOverviewCard';\nimport {\n BillingDateProvider,\n ConfigProvider,\n CurrencyProvider,\n FilterProvider,\n GroupsProvider,\n LoadingProvider,\n ScrollProvider,\n useFilters,\n useLastCompleteBillingDate,\n useLoading,\n} from '../../hooks';\nimport { CostInsightsThemeProvider } from '../CostInsightsPage/CostInsightsThemeProvider';\nimport { Progress, WarningPanel } from '@backstage/core-components';\nimport { default as MaterialAlert } from '@material-ui/lab/Alert';\nimport { mapLoadingToProps } from '../CostInsightsPage/selector';\nimport { intervalsOf } from '../../utils/duration';\nimport { costInsightsApiRef } from '../../api';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { Cost, Maybe } from '@backstage-community/plugin-cost-insights-common';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\n\nexport const EntityCostsCard = () => {\n const client = useApi(costInsightsApiRef);\n const { entity } = useEntity();\n\n const {\n loadingActions,\n loadingGroups,\n loadingBillingDate,\n loadingInitial,\n dispatchInitial,\n dispatchInsights,\n dispatchNone,\n } = useLoading(mapLoadingToProps);\n\n /* eslint-disable react-hooks/exhaustive-deps */\n // The dispatchLoading functions are derived from loading state using mapLoadingToProps, to\n // provide nicer props for the component. These are re-derived whenever loading state changes,\n // which causes an infinite loop as product panels load and re-trigger the useEffect below.\n // Since the functions don't change, we can memoize - but we trigger the same loop if we satisfy\n // exhaustive-deps by including the function itself in dependencies.\n\n const dispatchLoadingInitial = useCallback(dispatchInitial, []);\n const dispatchLoadingInsights = useCallback(dispatchInsights, []);\n const dispatchLoadingNone = useCallback(dispatchNone, []);\n /* eslint-enable react-hooks/exhaustive-deps */\n\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const [dailyCost, setDailyCost] = useState<Maybe<Cost>>(null);\n const [error, setError] = useState<Maybe<Error>>(null);\n const { pageFilters } = useFilters(p => p);\n\n useEffect(() => {\n async function getInsights() {\n setError(null);\n try {\n dispatchLoadingInsights(true);\n const intervals = intervalsOf(\n pageFilters.duration,\n lastCompleteBillingDate,\n 2,\n pageFilters.customDateRange,\n false, // Don't use comparison mode for entity costs\n );\n\n const fetchedDailyCost = await client.getCatalogEntityDailyCost!(\n stringifyEntityRef(entity),\n intervals,\n );\n setDailyCost(fetchedDailyCost);\n } catch (e) {\n setError(e);\n dispatchLoadingNone(loadingActions);\n } finally {\n dispatchLoadingNone(loadingActions);\n dispatchLoadingInitial(false);\n dispatchLoadingInsights(false);\n }\n }\n\n // Wait for metadata to finish loading\n if (!loadingBillingDate) {\n getInsights();\n }\n }, [\n client,\n entity,\n pageFilters,\n loadingActions,\n loadingGroups,\n loadingBillingDate,\n dispatchLoadingInsights,\n dispatchLoadingInitial,\n dispatchLoadingNone,\n lastCompleteBillingDate,\n ]);\n\n if (loadingInitial) {\n return <Progress />;\n }\n\n if (error) {\n return <MaterialAlert severity=\"error\">{error.message}</MaterialAlert>;\n }\n\n if (!dailyCost || !dailyCost.aggregation.length) {\n return <MaterialAlert severity=\"error\">No daily costs</MaterialAlert>;\n }\n\n return <CostOverviewCard dailyCostData={dailyCost} metricData={null} />;\n};\n\nexport const EntityCosts = () => {\n const client = useApi(costInsightsApiRef);\n\n if (!client.getCatalogEntityDailyCost) {\n return (\n <WarningPanel\n title=\"Could display costs for entity\"\n message=\"The getCatalogEntityDailyCost() method is not implemented by the costInsightsApi.\"\n />\n );\n }\n\n return (\n <CostInsightsThemeProvider>\n <ConfigProvider>\n <LoadingProvider>\n <GroupsProvider>\n <BillingDateProvider>\n <FilterProvider>\n <ScrollProvider>\n <CurrencyProvider>\n <EntityCostsCard />\n </CurrencyProvider>\n </ScrollProvider>\n </FilterProvider>\n </BillingDateProvider>\n </GroupsProvider>\n </LoadingProvider>\n </ConfigProvider>\n </CostInsightsThemeProvider>\n );\n};\n"],"names":["MaterialAlert"],"mappings":"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"}
@@ -0,0 +1,218 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useState } from 'react';
3
+ import Button from '@material-ui/core/Button';
4
+ import Popover from '@material-ui/core/Popover';
5
+ import Box from '@material-ui/core/Box';
6
+ import Chip from '@material-ui/core/Chip';
7
+ import Typography from '@material-ui/core/Typography';
8
+ import Divider from '@material-ui/core/Divider';
9
+ import { makeStyles } from '@material-ui/core/styles';
10
+ import CalendarTodayIcon from '@material-ui/icons/CalendarToday';
11
+ import { MuiPickersUtilsProvider } from '@material-ui/pickers/MuiPickersUtilsProvider';
12
+ import { KeyboardDatePicker } from '@material-ui/pickers/DatePicker';
13
+ import LuxonUtils from '@date-io/luxon';
14
+ import { DateTime } from 'luxon';
15
+ import '../../types/Alert.esm.js';
16
+ import { Duration } from '../../types/Duration.esm.js';
17
+ import { formatLastTwoLookaheadQuarters } from '../../utils/formatters.esm.js';
18
+ import '../../hooks/useConfig.esm.js';
19
+ import '../../hooks/useCurrency.esm.js';
20
+ import '../../hooks/useFilters.esm.js';
21
+ import '../../hooks/useGroups.esm.js';
22
+ import '../../hooks/useLoading.esm.js';
23
+ import '../../hooks/useScroll.esm.js';
24
+ import { useLastCompleteBillingDate } from '../../hooks/useLastCompleteBillingDate.esm.js';
25
+
26
+ const useStyles = makeStyles((theme) => ({
27
+ button: {
28
+ fontWeight: theme.typography.fontWeightBold,
29
+ textTransform: "none",
30
+ color: theme.palette.text.primary,
31
+ borderColor: theme.palette.divider,
32
+ "&:hover": {
33
+ borderColor: theme.palette.primary.main
34
+ }
35
+ },
36
+ popover: {
37
+ padding: theme.spacing(2),
38
+ minWidth: 320
39
+ },
40
+ presetSection: {
41
+ marginBottom: theme.spacing(2)
42
+ },
43
+ presetChip: {
44
+ margin: theme.spacing(0.5)
45
+ },
46
+ datePickersSection: {
47
+ marginTop: theme.spacing(2)
48
+ },
49
+ datePickerWrapper: {
50
+ marginBottom: theme.spacing(2),
51
+ "& .MuiTextField-root": {
52
+ width: "100%"
53
+ }
54
+ },
55
+ actionButtons: {
56
+ display: "flex",
57
+ justifyContent: "flex-end",
58
+ gap: theme.spacing(1),
59
+ marginTop: theme.spacing(2)
60
+ }
61
+ }));
62
+ function getDefaultOptions(lastCompleteBillingDate) {
63
+ return [
64
+ {
65
+ value: Duration.P90D,
66
+ label: "Past 6 Months"
67
+ },
68
+ {
69
+ value: Duration.P30D,
70
+ label: "Past 60 Days"
71
+ },
72
+ {
73
+ value: Duration.P3M,
74
+ label: formatLastTwoLookaheadQuarters(lastCompleteBillingDate)
75
+ }
76
+ ];
77
+ }
78
+ const DateRangePicker = ({
79
+ duration,
80
+ onSelect,
81
+ options,
82
+ customDateRange
83
+ }) => {
84
+ const classes = useStyles();
85
+ const lastCompleteBillingDate = useLastCompleteBillingDate();
86
+ const optionsOrDefault = options ?? getDefaultOptions(lastCompleteBillingDate);
87
+ const [anchorEl, setAnchorEl] = useState(null);
88
+ const [startDate, setStartDate] = useState(
89
+ customDateRange ? DateTime.fromISO(customDateRange.start) : null
90
+ );
91
+ const [endDate, setEndDate] = useState(
92
+ customDateRange ? DateTime.fromISO(customDateRange.end) : null
93
+ );
94
+ const handleClick = (event) => {
95
+ setAnchorEl(event.currentTarget);
96
+ };
97
+ const handleClose = () => {
98
+ setAnchorEl(null);
99
+ };
100
+ const handlePresetSelect = (preset) => {
101
+ onSelect(preset);
102
+ handleClose();
103
+ };
104
+ const handleCustomApply = () => {
105
+ if (startDate) {
106
+ const finalEndDate = endDate || DateTime.fromISO(lastCompleteBillingDate);
107
+ const customRange = {
108
+ start: startDate.toFormat("yyyy-MM-dd"),
109
+ end: finalEndDate.toFormat("yyyy-MM-dd")
110
+ };
111
+ onSelect(Duration.CUSTOM, customRange);
112
+ handleClose();
113
+ }
114
+ };
115
+ const open = Boolean(anchorEl);
116
+ const id = open ? "date-range-popover" : void 0;
117
+ const getDisplayLabel = () => {
118
+ if (duration === Duration.CUSTOM && customDateRange) {
119
+ return `${customDateRange.start} to ${customDateRange.end}`;
120
+ }
121
+ const option = optionsOrDefault.find((o) => o.value === duration);
122
+ return option?.label || "Select Period";
123
+ };
124
+ return /* @__PURE__ */ jsxs(MuiPickersUtilsProvider, { utils: LuxonUtils, children: [
125
+ /* @__PURE__ */ jsx(
126
+ Button,
127
+ {
128
+ className: classes.button,
129
+ variant: "outlined",
130
+ startIcon: /* @__PURE__ */ jsx(CalendarTodayIcon, {}),
131
+ onClick: handleClick,
132
+ "data-testid": "period-select",
133
+ children: getDisplayLabel()
134
+ }
135
+ ),
136
+ /* @__PURE__ */ jsx(
137
+ Popover,
138
+ {
139
+ id,
140
+ open,
141
+ anchorEl,
142
+ onClose: handleClose,
143
+ anchorOrigin: {
144
+ vertical: "bottom",
145
+ horizontal: "left"
146
+ },
147
+ transformOrigin: {
148
+ vertical: "top",
149
+ horizontal: "left"
150
+ },
151
+ children: /* @__PURE__ */ jsxs(Box, { className: classes.popover, children: [
152
+ /* @__PURE__ */ jsxs("div", { className: classes.presetSection, children: [
153
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Quick Select" }),
154
+ /* @__PURE__ */ jsx(Box, { display: "flex", flexWrap: "wrap", children: optionsOrDefault.map((option) => /* @__PURE__ */ jsx(
155
+ Chip,
156
+ {
157
+ label: option.label,
158
+ onClick: () => handlePresetSelect(option.value),
159
+ color: duration === option.value ? "primary" : "default",
160
+ className: classes.presetChip,
161
+ "data-testid": `period-preset-${option.value}`
162
+ },
163
+ option.value
164
+ )) })
165
+ ] }),
166
+ /* @__PURE__ */ jsx(Divider, {}),
167
+ /* @__PURE__ */ jsxs("div", { className: classes.datePickersSection, children: [
168
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Custom Date Range" }),
169
+ /* @__PURE__ */ jsx(Box, { className: classes.datePickerWrapper, children: /* @__PURE__ */ jsx(
170
+ KeyboardDatePicker,
171
+ {
172
+ label: "Start Date",
173
+ value: startDate,
174
+ onChange: (newValue) => setStartDate(newValue),
175
+ format: "yyyy-MM-dd",
176
+ variant: "inline",
177
+ inputVariant: "outlined",
178
+ size: "small"
179
+ }
180
+ ) }),
181
+ /* @__PURE__ */ jsx(Box, { className: classes.datePickerWrapper, children: /* @__PURE__ */ jsx(
182
+ KeyboardDatePicker,
183
+ {
184
+ label: "End Date (optional)",
185
+ value: endDate,
186
+ onChange: (newValue) => setEndDate(newValue),
187
+ minDate: startDate || void 0,
188
+ format: "yyyy-MM-dd",
189
+ variant: "inline",
190
+ inputVariant: "outlined",
191
+ size: "small",
192
+ helperText: endDate ? "" : "Defaults to yesterday"
193
+ }
194
+ ) }),
195
+ /* @__PURE__ */ jsxs(Box, { className: classes.actionButtons, children: [
196
+ /* @__PURE__ */ jsx(Button, { onClick: handleClose, size: "small", children: "Cancel" }),
197
+ /* @__PURE__ */ jsx(
198
+ Button,
199
+ {
200
+ onClick: handleCustomApply,
201
+ variant: "contained",
202
+ color: "primary",
203
+ size: "small",
204
+ disabled: !startDate,
205
+ "data-testid": "apply-custom-range",
206
+ children: "Apply"
207
+ }
208
+ )
209
+ ] })
210
+ ] })
211
+ ] })
212
+ }
213
+ )
214
+ ] });
215
+ };
216
+
217
+ export { DateRangePicker, getDefaultOptions };
218
+ //# sourceMappingURL=DateRangePicker.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DateRangePicker.esm.js","sources":["../../../src/components/PeriodSelect/DateRangePicker.tsx"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useState } from 'react';\nimport Button from '@material-ui/core/Button';\nimport Popover from '@material-ui/core/Popover';\nimport Box from '@material-ui/core/Box';\nimport Chip from '@material-ui/core/Chip';\nimport Typography from '@material-ui/core/Typography';\nimport Divider from '@material-ui/core/Divider';\nimport { makeStyles } from '@material-ui/core/styles';\nimport CalendarTodayIcon from '@material-ui/icons/CalendarToday';\nimport { MuiPickersUtilsProvider } from '@material-ui/pickers/MuiPickersUtilsProvider';\nimport { KeyboardDatePicker } from '@material-ui/pickers/DatePicker';\nimport LuxonUtils from '@date-io/luxon';\nimport { DateTime } from 'luxon';\nimport { Duration } from '../../types';\nimport { formatLastTwoLookaheadQuarters } from '../../utils/formatters';\nimport { useLastCompleteBillingDate } from '../../hooks';\n\nconst useStyles = makeStyles(theme => ({\n button: {\n fontWeight: theme.typography.fontWeightBold,\n textTransform: 'none',\n color: theme.palette.text.primary,\n borderColor: theme.palette.divider,\n '&:hover': {\n borderColor: theme.palette.primary.main,\n },\n },\n popover: {\n padding: theme.spacing(2),\n minWidth: 320,\n },\n presetSection: {\n marginBottom: theme.spacing(2),\n },\n presetChip: {\n margin: theme.spacing(0.5),\n },\n datePickersSection: {\n marginTop: theme.spacing(2),\n },\n datePickerWrapper: {\n marginBottom: theme.spacing(2),\n '& .MuiTextField-root': {\n width: '100%',\n },\n },\n actionButtons: {\n display: 'flex',\n justifyContent: 'flex-end',\n gap: theme.spacing(1),\n marginTop: theme.spacing(2),\n },\n}));\n\nexport type PeriodOption = {\n value: Duration;\n label: string;\n};\n\nexport function getDefaultOptions(\n lastCompleteBillingDate: string,\n): PeriodOption[] {\n return [\n {\n value: Duration.P90D,\n label: 'Past 6 Months',\n },\n {\n value: Duration.P30D,\n label: 'Past 60 Days',\n },\n {\n value: Duration.P3M,\n label: formatLastTwoLookaheadQuarters(lastCompleteBillingDate),\n },\n ];\n}\n\ntype DateRangePickerProps = {\n duration: Duration;\n onSelect: (\n duration: Duration,\n customDateRange?: { start: string; end: string },\n ) => void;\n options?: PeriodOption[];\n customDateRange?: { start: string; end: string };\n};\n\nexport const DateRangePicker = ({\n duration,\n onSelect,\n options,\n customDateRange,\n}: DateRangePickerProps) => {\n const classes = useStyles();\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const optionsOrDefault =\n options ?? getDefaultOptions(lastCompleteBillingDate);\n\n const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);\n const [startDate, setStartDate] = useState<DateTime | null>(\n customDateRange ? DateTime.fromISO(customDateRange.start) : null,\n );\n const [endDate, setEndDate] = useState<DateTime | null>(\n customDateRange ? DateTime.fromISO(customDateRange.end) : null,\n );\n\n const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(event.currentTarget);\n };\n\n const handleClose = () => {\n setAnchorEl(null);\n };\n\n const handlePresetSelect = (preset: Duration) => {\n onSelect(preset);\n handleClose();\n };\n\n const handleCustomApply = () => {\n if (startDate) {\n // If end date is not provided, default to lastCompleteBillingDate (day before today)\n const finalEndDate = endDate || DateTime.fromISO(lastCompleteBillingDate);\n const customRange = {\n start: startDate.toFormat('yyyy-MM-dd'),\n end: finalEndDate.toFormat('yyyy-MM-dd'),\n };\n onSelect(Duration.CUSTOM, customRange);\n handleClose();\n }\n };\n\n const open = Boolean(anchorEl);\n const id = open ? 'date-range-popover' : undefined;\n\n // Display label\n const getDisplayLabel = () => {\n if (duration === Duration.CUSTOM && customDateRange) {\n return `${customDateRange.start} to ${customDateRange.end}`;\n }\n const option = optionsOrDefault.find(o => o.value === duration);\n return option?.label || 'Select Period';\n };\n\n return (\n <MuiPickersUtilsProvider utils={LuxonUtils}>\n <Button\n className={classes.button}\n variant=\"outlined\"\n startIcon={<CalendarTodayIcon />}\n onClick={handleClick}\n data-testid=\"period-select\"\n >\n {getDisplayLabel()}\n </Button>\n <Popover\n id={id}\n open={open}\n anchorEl={anchorEl}\n onClose={handleClose}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'left',\n }}\n transformOrigin={{\n vertical: 'top',\n horizontal: 'left',\n }}\n >\n <Box className={classes.popover}>\n <div className={classes.presetSection}>\n <Typography variant=\"subtitle2\" gutterBottom>\n Quick Select\n </Typography>\n <Box display=\"flex\" flexWrap=\"wrap\">\n {optionsOrDefault.map(option => (\n <Chip\n key={option.value}\n label={option.label}\n onClick={() => handlePresetSelect(option.value)}\n color={duration === option.value ? 'primary' : 'default'}\n className={classes.presetChip}\n data-testid={`period-preset-${option.value}`}\n />\n ))}\n </Box>\n </div>\n\n <Divider />\n\n <div className={classes.datePickersSection}>\n <Typography variant=\"subtitle2\" gutterBottom>\n Custom Date Range\n </Typography>\n <Box className={classes.datePickerWrapper}>\n <KeyboardDatePicker\n label=\"Start Date\"\n value={startDate}\n onChange={newValue => setStartDate(newValue)}\n format=\"yyyy-MM-dd\"\n variant=\"inline\"\n inputVariant=\"outlined\"\n size=\"small\"\n />\n </Box>\n <Box className={classes.datePickerWrapper}>\n <KeyboardDatePicker\n label=\"End Date (optional)\"\n value={endDate}\n onChange={newValue => setEndDate(newValue)}\n minDate={startDate || undefined}\n format=\"yyyy-MM-dd\"\n variant=\"inline\"\n inputVariant=\"outlined\"\n size=\"small\"\n helperText={endDate ? '' : 'Defaults to yesterday'}\n />\n </Box>\n <Box className={classes.actionButtons}>\n <Button onClick={handleClose} size=\"small\">\n Cancel\n </Button>\n <Button\n onClick={handleCustomApply}\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={!startDate}\n data-testid=\"apply-custom-range\"\n >\n Apply\n </Button>\n </Box>\n </div>\n </Box>\n </Popover>\n </MuiPickersUtilsProvider>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -32,12 +32,19 @@ const ProductInsights = ({
32
32
  const dispatchLoading = useLoading(mapLoadingToProps);
33
33
  const dispatchLoadingProducts = useCallback(dispatchLoading, []);
34
34
  const onSelectAsyncMemo = useCallback(
35
- async function onSelectAsync(product, duration) {
35
+ async function onSelectAsync(product, duration, customDateRange) {
36
36
  return client.getProductInsights({
37
37
  group,
38
38
  project,
39
39
  product: product.kind,
40
- intervals: intervalsOf(duration, lastCompleteBillingDate)
40
+ intervals: intervalsOf(
41
+ duration,
42
+ lastCompleteBillingDate,
43
+ 2,
44
+ customDateRange,
45
+ true
46
+ // Use comparison mode for ProductInsights to split periods
47
+ )
41
48
  });
42
49
  },
43
50
  [client, group, project, lastCompleteBillingDate]
@@ -1 +1 @@
1
- {"version":3,"file":"ProductInsights.esm.js","sources":["../../../src/components/ProductInsights/ProductInsights.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport Box from '@material-ui/core/Box';\nimport Typography from '@material-ui/core/Typography';\nimport { default as Alert } from '@material-ui/lab/Alert';\nimport { costInsightsApiRef } from '../../api';\nimport { ProductInsightsCardList } from '../ProductInsightsCard/ProductInsightsCardList';\nimport { Duration } from '../../types';\nimport {\n Entity,\n Maybe,\n Product,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { intervalsOf, DEFAULT_DURATION } from '../../utils/duration';\nimport {\n DefaultLoadingAction,\n initialStatesOf,\n settledResponseOf,\n ProductState,\n} from '../../utils/loading';\nimport { totalAggregationSort } from '../../utils/sort';\nimport {\n useLoading,\n useLastCompleteBillingDate,\n MapLoadingToProps,\n} from '../../hooks';\nimport { useApi } from '@backstage/core-plugin-api';\n\ntype LoadingProps = (isLoading: boolean) => void;\n\nconst mapLoadingToProps: MapLoadingToProps<LoadingProps> =\n ({ dispatch }) =>\n (isLoading: boolean) =>\n dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading });\n\ntype ProductInsightsProps = {\n group: string;\n project: Maybe<string>;\n products: Product[];\n onLoaded: (entities: Product[]) => void;\n};\n\nexport const ProductInsights = ({\n group,\n project,\n products,\n onLoaded,\n}: ProductInsightsProps) => {\n const client = useApi(costInsightsApiRef);\n const onceRef = useRef(false);\n const [initialStates, setStates] = useState<ProductState[]>([]);\n const [error, setError] = useState<Maybe<Error>>(null);\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const dispatchLoading = useLoading(mapLoadingToProps);\n\n /* eslint-disable react-hooks/exhaustive-deps */\n // See @CostInsightsPage\n const dispatchLoadingProducts = useCallback(dispatchLoading, []);\n /* eslint-enable react-hooks/exhaustive-deps */\n\n const onSelectAsyncMemo = useCallback(\n async function onSelectAsync(\n product: Product,\n duration: Duration,\n ): Promise<Entity> {\n return client.getProductInsights({\n group: group,\n project: project,\n product: product.kind,\n intervals: intervalsOf(duration, lastCompleteBillingDate),\n });\n },\n [client, group, project, lastCompleteBillingDate],\n );\n\n useEffect(() => {\n async function getAllProductInsights() {\n try {\n dispatchLoadingProducts(true);\n const responses = await Promise.allSettled(\n products.map(product =>\n client.getProductInsights({\n group: group,\n project: project,\n product: product.kind,\n intervals: intervalsOf(DEFAULT_DURATION, lastCompleteBillingDate),\n }),\n ),\n ).then(settledResponseOf);\n\n const updatedInitialStates = initialStatesOf(products, responses).sort(\n totalAggregationSort,\n );\n setStates(updatedInitialStates);\n } catch (e) {\n setError(e);\n } finally {\n dispatchLoadingProducts(false);\n }\n }\n\n getAllProductInsights();\n }, [\n client,\n group,\n project,\n products,\n lastCompleteBillingDate,\n dispatchLoadingProducts,\n ]);\n\n useEffect(\n function handleOnLoaded() {\n if (onceRef.current) {\n const initialProducts = initialStates.map(state => state.product);\n onLoaded(initialProducts);\n } else {\n onceRef.current = true;\n }\n },\n [initialStates, onLoaded],\n );\n\n return (\n <Box px={3} py={6}>\n <Box mt={0} mb={5} textAlign=\"center\">\n <Typography variant=\"h4\" gutterBottom>\n Your team's product usage\n </Typography>\n </Box>\n {error ? (\n <Alert severity=\"error\">{error.message}</Alert>\n ) : (\n <ProductInsightsCardList\n initialStates={initialStates}\n onSelectAsync={onSelectAsyncMemo}\n />\n )}\n </Box>\n );\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ProductInsights.esm.js","sources":["../../../src/components/ProductInsights/ProductInsights.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport Box from '@material-ui/core/Box';\nimport Typography from '@material-ui/core/Typography';\nimport { default as Alert } from '@material-ui/lab/Alert';\nimport { costInsightsApiRef } from '../../api';\nimport { ProductInsightsCardList } from '../ProductInsightsCard/ProductInsightsCardList';\nimport { Duration } from '../../types';\nimport {\n Entity,\n Maybe,\n Product,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { intervalsOf, DEFAULT_DURATION } from '../../utils/duration';\nimport {\n DefaultLoadingAction,\n initialStatesOf,\n settledResponseOf,\n ProductState,\n} from '../../utils/loading';\nimport { totalAggregationSort } from '../../utils/sort';\nimport {\n useLoading,\n useLastCompleteBillingDate,\n MapLoadingToProps,\n} from '../../hooks';\nimport { useApi } from '@backstage/core-plugin-api';\n\ntype LoadingProps = (isLoading: boolean) => void;\n\nconst mapLoadingToProps: MapLoadingToProps<LoadingProps> =\n ({ dispatch }) =>\n (isLoading: boolean) =>\n dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading });\n\ntype ProductInsightsProps = {\n group: string;\n project: Maybe<string>;\n products: Product[];\n onLoaded: (entities: Product[]) => void;\n};\n\nexport const ProductInsights = ({\n group,\n project,\n products,\n onLoaded,\n}: ProductInsightsProps) => {\n const client = useApi(costInsightsApiRef);\n const onceRef = useRef(false);\n const [initialStates, setStates] = useState<ProductState[]>([]);\n const [error, setError] = useState<Maybe<Error>>(null);\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const dispatchLoading = useLoading(mapLoadingToProps);\n\n /* eslint-disable react-hooks/exhaustive-deps */\n // See @CostInsightsPage\n const dispatchLoadingProducts = useCallback(dispatchLoading, []);\n /* eslint-enable react-hooks/exhaustive-deps */\n\n const onSelectAsyncMemo = useCallback(\n async function onSelectAsync(\n product: Product,\n duration: Duration,\n customDateRange?: { start: string; end: string },\n ): Promise<Entity> {\n return client.getProductInsights({\n group: group,\n project: project,\n product: product.kind,\n intervals: intervalsOf(\n duration,\n lastCompleteBillingDate,\n 2,\n customDateRange,\n true, // Use comparison mode for ProductInsights to split periods\n ),\n });\n },\n [client, group, project, lastCompleteBillingDate],\n );\n\n useEffect(() => {\n async function getAllProductInsights() {\n try {\n dispatchLoadingProducts(true);\n const responses = await Promise.allSettled(\n products.map(product =>\n client.getProductInsights({\n group: group,\n project: project,\n product: product.kind,\n intervals: intervalsOf(DEFAULT_DURATION, lastCompleteBillingDate),\n }),\n ),\n ).then(settledResponseOf);\n\n const updatedInitialStates = initialStatesOf(products, responses).sort(\n totalAggregationSort,\n );\n setStates(updatedInitialStates);\n } catch (e) {\n setError(e);\n } finally {\n dispatchLoadingProducts(false);\n }\n }\n\n getAllProductInsights();\n }, [\n client,\n group,\n project,\n products,\n lastCompleteBillingDate,\n dispatchLoadingProducts,\n ]);\n\n useEffect(\n function handleOnLoaded() {\n if (onceRef.current) {\n const initialProducts = initialStates.map(state => state.product);\n onLoaded(initialProducts);\n } else {\n onceRef.current = true;\n }\n },\n [initialStates, onLoaded],\n );\n\n return (\n <Box px={3} py={6}>\n <Box mt={0} mb={5} textAlign=\"center\">\n <Typography variant=\"h4\" gutterBottom>\n Your team's product usage\n </Typography>\n </Box>\n {error ? (\n <Alert severity=\"error\">{error.message}</Alert>\n ) : (\n <ProductInsightsCardList\n initialStates={initialStates}\n onSelectAsync={onSelectAsyncMemo}\n />\n )}\n </Box>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -3,10 +3,13 @@ import { useRef, useState, useCallback, useEffect } from 'react';
3
3
  import pluralize from 'pluralize';
4
4
  import Typography from '@material-ui/core/Typography';
5
5
  import Alert from '@material-ui/lab/Alert';
6
- import { PeriodSelect } from '../PeriodSelect/PeriodSelect.esm.js';
7
- import { ProductInsightsChart } from './ProductInsightsChart.esm.js';
6
+ import '@material-ui/core/MenuItem';
7
+ import '@material-ui/core/Select';
8
+ import '../../types/Alert.esm.js';
9
+ import 'luxon';
10
+ import '../../utils/duration.esm.js';
11
+ import { findAnyKey } from '../../utils/assert.esm.js';
8
12
  import { useProductInsightsCardStyles } from '../../utils/styles.esm.js';
9
- import { DefaultLoadingAction } from '../../utils/loading.esm.js';
10
13
  import '../../hooks/useConfig.esm.js';
11
14
  import '../../hooks/useCurrency.esm.js';
12
15
  import '../../hooks/useFilters.esm.js';
@@ -14,7 +17,9 @@ import '../../hooks/useGroups.esm.js';
14
17
  import { useLoading } from '../../hooks/useLoading.esm.js';
15
18
  import '../../hooks/useScroll.esm.js';
16
19
  import { useLastCompleteBillingDate } from '../../hooks/useLastCompleteBillingDate.esm.js';
17
- import { findAnyKey } from '../../utils/assert.esm.js';
20
+ import { DateRangePicker } from '../PeriodSelect/DateRangePicker.esm.js';
21
+ import { ProductInsightsChart } from './ProductInsightsChart.esm.js';
22
+ import { DefaultLoadingAction } from '../../utils/loading.esm.js';
18
23
  import { ScrollAnchor } from '../../utils/scroll.esm.js';
19
24
  import { InfoCard } from '@backstage/core-components';
20
25
 
@@ -31,12 +36,13 @@ const ProductInsightsCard = ({
31
36
  const lastCompleteBillingDate = useLastCompleteBillingDate();
32
37
  const [entity, setEntity] = useState(initialState.entity);
33
38
  const [duration, setDuration] = useState(initialState.duration);
39
+ const [customDateRange, setCustomDateRange] = useState(void 0);
34
40
  const dispatchLoadingProduct = useCallback(dispatchLoading, []);
35
41
  useEffect(() => {
36
42
  async function handleOnSelectAsync() {
37
43
  dispatchLoadingProduct(true);
38
44
  try {
39
- const e = await onSelectAsync(product, duration);
45
+ const e = await onSelectAsync(product, duration, customDateRange);
40
46
  setEntity(e);
41
47
  } catch (e) {
42
48
  setEntity(null);
@@ -50,13 +56,30 @@ const ProductInsightsCard = ({
50
56
  } else {
51
57
  mountedRef.current = true;
52
58
  }
53
- }, [product, duration, onSelectAsync, dispatchLoadingProduct]);
59
+ }, [
60
+ product,
61
+ duration,
62
+ customDateRange,
63
+ onSelectAsync,
64
+ dispatchLoadingProduct
65
+ ]);
54
66
  const entityKey = findAnyKey(entity?.entities);
55
67
  const entities = entityKey ? entity.entities[entityKey] : [];
56
68
  const subheader = entityKey && entities.length ? `${pluralize(entityKey, entities.length, true)}, sorted by cost` : null;
69
+ const handleDurationSelect = (newDuration, newCustomDateRange) => {
70
+ setDuration(newDuration);
71
+ setCustomDateRange(newCustomDateRange);
72
+ };
57
73
  const headerProps = {
58
74
  classes,
59
- action: /* @__PURE__ */ jsx(PeriodSelect, { duration, onSelect: setDuration })
75
+ action: /* @__PURE__ */ jsx(
76
+ DateRangePicker,
77
+ {
78
+ duration,
79
+ onSelect: handleDurationSelect,
80
+ customDateRange
81
+ }
82
+ )
60
83
  };
61
84
  if (error || !entity) {
62
85
  return /* @__PURE__ */ jsxs(InfoCard, { title: product.name, headerProps, children: [
@@ -77,7 +100,8 @@ const ProductInsightsCard = ({
77
100
  {
78
101
  entity,
79
102
  duration,
80
- billingDate: lastCompleteBillingDate
103
+ billingDate: lastCompleteBillingDate,
104
+ customDateRange
81
105
  }
82
106
  ) : /* @__PURE__ */ jsxs(Typography, { children: [
83
107
  "There are no ",
@@ -1 +1 @@
1
- {"version":3,"file":"ProductInsightsCard.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductInsightsCard.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PropsWithChildren,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport pluralize from 'pluralize';\nimport Typography from '@material-ui/core/Typography';\nimport { default as Alert } from '@material-ui/lab/Alert';\nimport { PeriodSelect } from '../PeriodSelect';\nimport { ProductInsightsChart } from './ProductInsightsChart';\nimport { useProductInsightsCardStyles as useStyles } from '../../utils/styles';\nimport { DefaultLoadingAction } from '../../utils/loading';\nimport { Duration } from '../../types';\nimport {\n Entity,\n Maybe,\n Product,\n} from '@backstage-community/plugin-cost-insights-common';\nimport {\n MapLoadingToProps,\n useLastCompleteBillingDate,\n useLoading,\n} from '../../hooks';\nimport { findAnyKey } from '../../utils/assert';\nimport { ScrollAnchor } from '../../utils/scroll';\nimport { InfoCard } from '@backstage/core-components';\n\ntype LoadingProps = (isLoading: boolean) => void;\n\nexport type ProductInsightsCardProps = {\n product: Product;\n initialState: {\n entity: Maybe<Entity>;\n duration: Duration;\n };\n onSelectAsync: (product: Product, duration: Duration) => Promise<Entity>;\n};\n\nconst mapLoadingToProps: MapLoadingToProps<LoadingProps> =\n ({ dispatch }) =>\n (isLoading: boolean) =>\n dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading });\n\nexport const ProductInsightsCard = ({\n initialState,\n product,\n onSelectAsync,\n}: PropsWithChildren<ProductInsightsCardProps>) => {\n const classes = useStyles();\n const mountedRef = useRef(false);\n const [error, setError] = useState<Maybe<Error>>(null);\n const dispatchLoading = useLoading(mapLoadingToProps);\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const [entity, setEntity] = useState<Maybe<Entity>>(initialState.entity);\n const [duration, setDuration] = useState<Duration>(initialState.duration);\n\n /* eslint-disable react-hooks/exhaustive-deps */\n const dispatchLoadingProduct = useCallback(dispatchLoading, []);\n /* eslint-enable react-hooks/exhaustive-deps */\n\n useEffect(() => {\n async function handleOnSelectAsync() {\n dispatchLoadingProduct(true);\n try {\n const e = await onSelectAsync(product, duration);\n setEntity(e);\n } catch (e) {\n setEntity(null);\n setError(e);\n } finally {\n dispatchLoadingProduct(false);\n }\n }\n\n if (mountedRef.current) {\n handleOnSelectAsync();\n } else {\n mountedRef.current = true;\n }\n }, [product, duration, onSelectAsync, dispatchLoadingProduct]);\n\n // Only a single entities Record for the root product entity is supported\n const entityKey = findAnyKey(entity?.entities);\n const entities = entityKey ? entity!.entities[entityKey] : [];\n\n const subheader =\n entityKey && entities.length\n ? `${pluralize(entityKey, entities.length, true)}, sorted by cost`\n : null;\n const headerProps = {\n classes: classes,\n action: <PeriodSelect duration={duration} onSelect={setDuration} />,\n };\n\n if (error || !entity) {\n return (\n <InfoCard title={product.name} headerProps={headerProps}>\n <ScrollAnchor id={product.kind} />\n <Alert severity=\"error\">\n {error\n ? error.message\n : `Error: Could not fetch product insights for ${product.name}`}\n </Alert>\n </InfoCard>\n );\n }\n\n return (\n <InfoCard\n title={product.name}\n subheader={subheader}\n headerProps={headerProps}\n >\n <ScrollAnchor id={product.kind} />\n {entities.length ? (\n <ProductInsightsChart\n entity={entity}\n duration={duration}\n billingDate={lastCompleteBillingDate}\n />\n ) : (\n <Typography>\n There are no {product.name} costs within this time frame for your\n team's projects.\n </Typography>\n )}\n </InfoCard>\n );\n};\n"],"names":["useStyles"],"mappings":"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"}
1
+ {"version":3,"file":"ProductInsightsCard.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductInsightsCard.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PropsWithChildren,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport pluralize from 'pluralize';\nimport Typography from '@material-ui/core/Typography';\nimport { default as Alert } from '@material-ui/lab/Alert';\nimport { DateRangePicker } from '../PeriodSelect';\nimport { ProductInsightsChart } from './ProductInsightsChart';\nimport { useProductInsightsCardStyles as useStyles } from '../../utils/styles';\nimport { DefaultLoadingAction } from '../../utils/loading';\nimport { Duration } from '../../types';\nimport {\n Entity,\n Maybe,\n Product,\n} from '@backstage-community/plugin-cost-insights-common';\nimport {\n MapLoadingToProps,\n useLastCompleteBillingDate,\n useLoading,\n} from '../../hooks';\nimport { findAnyKey } from '../../utils/assert';\nimport { ScrollAnchor } from '../../utils/scroll';\nimport { InfoCard } from '@backstage/core-components';\n\ntype LoadingProps = (isLoading: boolean) => void;\n\nexport type ProductInsightsCardProps = {\n product: Product;\n initialState: {\n entity: Maybe<Entity>;\n duration: Duration;\n };\n onSelectAsync: (\n product: Product,\n duration: Duration,\n customDateRange?: { start: string; end: string },\n ) => Promise<Entity>;\n};\n\nconst mapLoadingToProps: MapLoadingToProps<LoadingProps> =\n ({ dispatch }) =>\n (isLoading: boolean) =>\n dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading });\n\nexport const ProductInsightsCard = ({\n initialState,\n product,\n onSelectAsync,\n}: PropsWithChildren<ProductInsightsCardProps>) => {\n const classes = useStyles();\n const mountedRef = useRef(false);\n const [error, setError] = useState<Maybe<Error>>(null);\n const dispatchLoading = useLoading(mapLoadingToProps);\n const lastCompleteBillingDate = useLastCompleteBillingDate();\n const [entity, setEntity] = useState<Maybe<Entity>>(initialState.entity);\n const [duration, setDuration] = useState<Duration>(initialState.duration);\n const [customDateRange, setCustomDateRange] = useState<\n { start: string; end: string } | undefined\n >(undefined);\n\n /* eslint-disable react-hooks/exhaustive-deps */\n const dispatchLoadingProduct = useCallback(dispatchLoading, []);\n /* eslint-enable react-hooks/exhaustive-deps */\n\n useEffect(() => {\n async function handleOnSelectAsync() {\n dispatchLoadingProduct(true);\n try {\n const e = await onSelectAsync(product, duration, customDateRange);\n setEntity(e);\n } catch (e) {\n setEntity(null);\n setError(e);\n } finally {\n dispatchLoadingProduct(false);\n }\n }\n\n if (mountedRef.current) {\n handleOnSelectAsync();\n } else {\n mountedRef.current = true;\n }\n }, [\n product,\n duration,\n customDateRange,\n onSelectAsync,\n dispatchLoadingProduct,\n ]);\n\n // Only a single entities Record for the root product entity is supported\n const entityKey = findAnyKey(entity?.entities);\n const entities = entityKey ? entity!.entities[entityKey] : [];\n\n const subheader =\n entityKey && entities.length\n ? `${pluralize(entityKey, entities.length, true)}, sorted by cost`\n : null;\n const handleDurationSelect = (\n newDuration: Duration,\n newCustomDateRange?: { start: string; end: string },\n ) => {\n setDuration(newDuration);\n setCustomDateRange(newCustomDateRange);\n };\n\n const headerProps = {\n classes: classes,\n action: (\n <DateRangePicker\n duration={duration}\n onSelect={handleDurationSelect}\n customDateRange={customDateRange}\n />\n ),\n };\n\n if (error || !entity) {\n return (\n <InfoCard title={product.name} headerProps={headerProps}>\n <ScrollAnchor id={product.kind} />\n <Alert severity=\"error\">\n {error\n ? error.message\n : `Error: Could not fetch product insights for ${product.name}`}\n </Alert>\n </InfoCard>\n );\n }\n\n return (\n <InfoCard\n title={product.name}\n subheader={subheader}\n headerProps={headerProps}\n >\n <ScrollAnchor id={product.kind} />\n {entities.length ? (\n <ProductInsightsChart\n entity={entity}\n duration={duration}\n billingDate={lastCompleteBillingDate}\n customDateRange={customDateRange}\n />\n ) : (\n <Typography>\n There are no {product.name} costs within this time frame for your\n team's projects.\n </Typography>\n )}\n </InfoCard>\n );\n};\n"],"names":["useStyles"],"mappings":"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"}
@@ -28,7 +28,8 @@ import '../../hooks/useLastCompleteBillingDate.esm.js';
28
28
  const ProductInsightsChart = ({
29
29
  billingDate,
30
30
  entity,
31
- duration
31
+ duration,
32
+ customDateRange
32
33
  }) => {
33
34
  const classes = useProductInsightsChartStyles();
34
35
  const layoutClasses = useBarChartLayoutStyles();
@@ -50,8 +51,20 @@ const ProductInsightsChart = ({
50
51
  const costEnd = entity.aggregation[1];
51
52
  const resources = entities.map(resourceOf);
52
53
  const options = {
53
- previousName: formatPeriod(duration, billingDate, false),
54
- currentName: formatPeriod(duration, billingDate, true)
54
+ previousName: formatPeriod(
55
+ duration,
56
+ billingDate,
57
+ false,
58
+ customDateRange,
59
+ true
60
+ ),
61
+ currentName: formatPeriod(
62
+ duration,
63
+ billingDate,
64
+ true,
65
+ customDateRange,
66
+ true
67
+ )
55
68
  };
56
69
  const onMouseMove = (data) => {
57
70
  if (isLabeled(data)) {
@@ -127,7 +140,14 @@ const ProductInsightsChart = ({
127
140
  LegendItem,
128
141
  {
129
142
  title: choose(["Cost Savings", "Cost Excess"], entity.change),
130
- children: /* @__PURE__ */ jsx(CostGrowth, { change: entity.change, duration })
143
+ children: /* @__PURE__ */ jsx(
144
+ CostGrowth,
145
+ {
146
+ change: entity.change,
147
+ duration,
148
+ customDateRange
149
+ }
150
+ )
131
151
  }
132
152
  ) }),
133
153
  /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"file":"ProductInsightsChart.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductInsightsChart.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useMemo, useState } from 'react';\nimport pluralize from 'pluralize';\nimport Box from '@material-ui/core/Box';\nimport Typography from '@material-ui/core/Typography';\nimport { default as FullScreenIcon } from '@material-ui/icons/Fullscreen';\nimport { LegendItem } from '../LegendItem';\nimport { ProductEntityDialog } from './ProductEntityDialog';\nimport { CostGrowth, CostGrowthIndicator } from '../CostGrowth';\nimport {\n BarChart,\n BarChartLegend,\n BarChartTooltip,\n BarChartTooltipItem,\n BarChartLegendOptions,\n} from '../BarChart';\nimport {\n findAlways,\n notEmpty,\n isUndefined,\n findAnyKey,\n assertAlways,\n} from '../../utils/assert';\nimport { formatPeriod, formatChange } from '../../utils/formatters';\nimport {\n titleOf,\n tooltipItemOf,\n resourceOf,\n isInvalid,\n isLabeled,\n isUnlabeled,\n} from '../../utils/graphs';\nimport {\n useProductInsightsChartStyles as useStyles,\n useBarChartLayoutStyles as useLayoutStyles,\n} from '../../utils/styles';\nimport { Duration } from '../../types';\nimport {\n Entity,\n Maybe,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { choose } from '../../utils/change';\nimport { TooltipRenderer } from '../../types';\nimport { useConfig } from '../../hooks';\n\nexport type ProductInsightsChartProps = {\n billingDate: string;\n entity: Entity;\n duration: Duration;\n};\n\nexport const ProductInsightsChart = ({\n billingDate,\n entity,\n duration,\n}: ProductInsightsChartProps) => {\n const classes = useStyles();\n const layoutClasses = useLayoutStyles();\n const { baseCurrency } = useConfig();\n\n // Only a single entities Record for the root product entity is supported\n const entities = useMemo(() => {\n const entityLabel = assertAlways(findAnyKey(entity.entities));\n return entity.entities[entityLabel] ?? [];\n }, [entity]);\n\n const [activeLabel, setActive] = useState<Maybe<string>>();\n const [selectLabel, setSelected] = useState<Maybe<string>>();\n const isSelected = useMemo(() => !isUndefined(selectLabel), [selectLabel]);\n\n const isClickable = useMemo(() => {\n const breakdowns = Object.keys(\n entities.find(e => e.id === activeLabel)?.entities ?? {},\n );\n return breakdowns.length > 0;\n }, [entities, activeLabel]);\n\n const costStart = entity.aggregation[0];\n const costEnd = entity.aggregation[1];\n const resources = entities.map(resourceOf);\n\n const options: Partial<BarChartLegendOptions> = {\n previousName: formatPeriod(duration, billingDate, false),\n currentName: formatPeriod(duration, billingDate, true),\n };\n\n const onMouseMove: (...args: any[]) => void = (\n data: Record<'activeLabel', string | undefined>,\n ) => {\n if (isLabeled(data)) {\n setActive(data.activeLabel!);\n } else if (isUnlabeled(data)) {\n setActive(null);\n } else {\n setActive(undefined);\n }\n };\n\n const onClick: (...args: any[]) => void = (\n data: Record<'activeLabel', string>,\n ) => {\n if (isLabeled(data)) {\n setSelected(data.activeLabel);\n } else if (isUnlabeled(data)) {\n setSelected(null);\n } else {\n setSelected(undefined);\n }\n };\n\n const renderProductInsightsTooltip: TooltipRenderer = ({\n label,\n payload = [],\n }) => {\n /* Labels and payloads may be undefined or empty */\n if (isInvalid({ label, payload })) return null;\n\n /*\n * recharts coerces null values to strings\n * entity -> resource -> payload\n * { id: null } -> { name: null } -> { label: '' }\n */\n const id = label === '' ? null : label;\n\n const title = titleOf(label);\n const items = payload.map(tooltipItemOf(baseCurrency)).filter(notEmpty);\n\n const activeEntity = findAlways(entities, e => e.id === id);\n const breakdowns = Object.keys(activeEntity.entities);\n\n if (breakdowns.length) {\n const subtitle = breakdowns\n .map(b => pluralize(b, activeEntity.entities[b].length, true))\n .join(', ');\n return (\n <BarChartTooltip\n title={title}\n subtitle={subtitle}\n topRight={\n !!activeEntity.change.ratio && (\n <CostGrowthIndicator\n formatter={formatChange}\n change={activeEntity.change}\n className={classes.indicator}\n />\n )\n }\n actions={\n <Box className={classes.actions}>\n <FullScreenIcon />\n <Typography>Click for breakdown</Typography>\n </Box>\n }\n >\n {items.map((item, index) => (\n <BarChartTooltipItem key={`${item.label}-${index}`} item={item} />\n ))}\n </BarChartTooltip>\n );\n }\n\n // If an entity doesn't have any sub-entities, there aren't any costs to break down.\n return (\n <BarChartTooltip\n title={title}\n topRight={\n !!activeEntity.change.ratio && (\n <CostGrowthIndicator\n formatter={formatChange}\n change={activeEntity.change}\n className={classes.indicator}\n />\n )\n }\n content={\n id\n ? null\n : \"This product has costs that are not labeled and therefore can't be attributed to a specific entity.\"\n }\n >\n {items.map((item, index) => (\n <BarChartTooltipItem key={`${item.label}-${index}`} item={item} />\n ))}\n </BarChartTooltip>\n );\n };\n\n const barChartProps = isClickable ? { onClick } : {};\n\n return (\n <Box className={layoutClasses.wrapper}>\n <BarChartLegend costStart={costStart} costEnd={costEnd} options={options}>\n <LegendItem\n title={choose(['Cost Savings', 'Cost Excess'], entity.change)}\n >\n <CostGrowth change={entity.change} duration={duration} />\n </LegendItem>\n </BarChartLegend>\n <BarChart\n resources={resources}\n tooltip={renderProductInsightsTooltip}\n onMouseMove={onMouseMove}\n options={options}\n {...barChartProps}\n />\n {isSelected && entities.length && (\n <ProductEntityDialog\n open={isSelected}\n onClose={() => setSelected(undefined)}\n entity={findAlways(entities, e => e.id === selectLabel)}\n options={options}\n />\n )}\n </Box>\n );\n};\n"],"names":["useStyles","useLayoutStyles"],"mappings":"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"}
1
+ {"version":3,"file":"ProductInsightsChart.esm.js","sources":["../../../src/components/ProductInsightsCard/ProductInsightsChart.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useMemo, useState } from 'react';\nimport pluralize from 'pluralize';\nimport Box from '@material-ui/core/Box';\nimport Typography from '@material-ui/core/Typography';\nimport { default as FullScreenIcon } from '@material-ui/icons/Fullscreen';\nimport { LegendItem } from '../LegendItem';\nimport { ProductEntityDialog } from './ProductEntityDialog';\nimport { CostGrowth, CostGrowthIndicator } from '../CostGrowth';\nimport {\n BarChart,\n BarChartLegend,\n BarChartTooltip,\n BarChartTooltipItem,\n BarChartLegendOptions,\n} from '../BarChart';\nimport {\n findAlways,\n notEmpty,\n isUndefined,\n findAnyKey,\n assertAlways,\n} from '../../utils/assert';\nimport { formatPeriod, formatChange } from '../../utils/formatters';\nimport {\n titleOf,\n tooltipItemOf,\n resourceOf,\n isInvalid,\n isLabeled,\n isUnlabeled,\n} from '../../utils/graphs';\nimport {\n useProductInsightsChartStyles as useStyles,\n useBarChartLayoutStyles as useLayoutStyles,\n} from '../../utils/styles';\nimport { Duration } from '../../types';\nimport {\n Entity,\n Maybe,\n} from '@backstage-community/plugin-cost-insights-common';\nimport { choose } from '../../utils/change';\nimport { TooltipRenderer } from '../../types';\nimport { useConfig } from '../../hooks';\n\nexport type ProductInsightsChartProps = {\n billingDate: string;\n entity: Entity;\n duration: Duration;\n customDateRange?: { start: string; end: string };\n};\n\nexport const ProductInsightsChart = ({\n billingDate,\n entity,\n duration,\n customDateRange,\n}: ProductInsightsChartProps) => {\n const classes = useStyles();\n const layoutClasses = useLayoutStyles();\n const { baseCurrency } = useConfig();\n\n // Only a single entities Record for the root product entity is supported\n const entities = useMemo(() => {\n const entityLabel = assertAlways(findAnyKey(entity.entities));\n return entity.entities[entityLabel] ?? [];\n }, [entity]);\n\n const [activeLabel, setActive] = useState<Maybe<string>>();\n const [selectLabel, setSelected] = useState<Maybe<string>>();\n const isSelected = useMemo(() => !isUndefined(selectLabel), [selectLabel]);\n\n const isClickable = useMemo(() => {\n const breakdowns = Object.keys(\n entities.find(e => e.id === activeLabel)?.entities ?? {},\n );\n return breakdowns.length > 0;\n }, [entities, activeLabel]);\n\n const costStart = entity.aggregation[0];\n const costEnd = entity.aggregation[1];\n const resources = entities.map(resourceOf);\n\n const options: Partial<BarChartLegendOptions> = {\n previousName: formatPeriod(\n duration,\n billingDate,\n false,\n customDateRange,\n true,\n ),\n currentName: formatPeriod(\n duration,\n billingDate,\n true,\n customDateRange,\n true,\n ),\n };\n\n const onMouseMove: (...args: any[]) => void = (\n data: Record<'activeLabel', string | undefined>,\n ) => {\n if (isLabeled(data)) {\n setActive(data.activeLabel!);\n } else if (isUnlabeled(data)) {\n setActive(null);\n } else {\n setActive(undefined);\n }\n };\n\n const onClick: (...args: any[]) => void = (\n data: Record<'activeLabel', string>,\n ) => {\n if (isLabeled(data)) {\n setSelected(data.activeLabel);\n } else if (isUnlabeled(data)) {\n setSelected(null);\n } else {\n setSelected(undefined);\n }\n };\n\n const renderProductInsightsTooltip: TooltipRenderer = ({\n label,\n payload = [],\n }) => {\n /* Labels and payloads may be undefined or empty */\n if (isInvalid({ label, payload })) return null;\n\n /*\n * recharts coerces null values to strings\n * entity -> resource -> payload\n * { id: null } -> { name: null } -> { label: '' }\n */\n const id = label === '' ? null : label;\n\n const title = titleOf(label);\n const items = payload.map(tooltipItemOf(baseCurrency)).filter(notEmpty);\n\n const activeEntity = findAlways(entities, e => e.id === id);\n const breakdowns = Object.keys(activeEntity.entities);\n\n if (breakdowns.length) {\n const subtitle = breakdowns\n .map(b => pluralize(b, activeEntity.entities[b].length, true))\n .join(', ');\n return (\n <BarChartTooltip\n title={title}\n subtitle={subtitle}\n topRight={\n !!activeEntity.change.ratio && (\n <CostGrowthIndicator\n formatter={formatChange}\n change={activeEntity.change}\n className={classes.indicator}\n />\n )\n }\n actions={\n <Box className={classes.actions}>\n <FullScreenIcon />\n <Typography>Click for breakdown</Typography>\n </Box>\n }\n >\n {items.map((item, index) => (\n <BarChartTooltipItem key={`${item.label}-${index}`} item={item} />\n ))}\n </BarChartTooltip>\n );\n }\n\n // If an entity doesn't have any sub-entities, there aren't any costs to break down.\n return (\n <BarChartTooltip\n title={title}\n topRight={\n !!activeEntity.change.ratio && (\n <CostGrowthIndicator\n formatter={formatChange}\n change={activeEntity.change}\n className={classes.indicator}\n />\n )\n }\n content={\n id\n ? null\n : \"This product has costs that are not labeled and therefore can't be attributed to a specific entity.\"\n }\n >\n {items.map((item, index) => (\n <BarChartTooltipItem key={`${item.label}-${index}`} item={item} />\n ))}\n </BarChartTooltip>\n );\n };\n\n const barChartProps = isClickable ? { onClick } : {};\n\n return (\n <Box className={layoutClasses.wrapper}>\n <BarChartLegend costStart={costStart} costEnd={costEnd} options={options}>\n <LegendItem\n title={choose(['Cost Savings', 'Cost Excess'], entity.change)}\n >\n <CostGrowth\n change={entity.change}\n duration={duration}\n customDateRange={customDateRange}\n />\n </LegendItem>\n </BarChartLegend>\n <BarChart\n resources={resources}\n tooltip={renderProductInsightsTooltip}\n onMouseMove={onMouseMove}\n options={options}\n {...barChartProps}\n />\n {isSelected && entities.length && (\n <ProductEntityDialog\n open={isSelected}\n onClose={() => setSelected(undefined)}\n entity={findAlways(entities, e => e.id === selectLabel)}\n options={options}\n />\n )}\n </Box>\n );\n};\n"],"names":["useStyles","useLayoutStyles"],"mappings":"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"}
@@ -20,6 +20,7 @@ import '../../hooks/useGroups.esm.js';
20
20
  import '../../hooks/useLoading.esm.js';
21
21
  import '../../hooks/useScroll.esm.js';
22
22
  import '../../hooks/useLastCompleteBillingDate.esm.js';
23
+ import '../PeriodSelect/DateRangePicker.esm.js';
23
24
  import { ProductInsightsChart } from '../ProductInsightsCard/ProductInsightsChart.esm.js';
24
25
  import { InfoCard } from '@backstage/core-components';
25
26
  import { ProjectGrowthAlert } from '../../alerts/ProjectGrowthAlert.esm.js';