@backstage-community/plugin-cost-insights 0.12.24

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.
@@ -0,0 +1,594 @@
1
+ import React, { useRef, useEffect, useState } from 'react';
2
+ import Box from '@material-ui/core/Box';
3
+ import { capitalize } from '@material-ui/core/utils';
4
+ import Card from '@material-ui/core/Card';
5
+ import CardContent from '@material-ui/core/CardContent';
6
+ import Divider from '@material-ui/core/Divider';
7
+ import Tab from '@material-ui/core/Tab';
8
+ import Tabs from '@material-ui/core/Tabs';
9
+ import { useTheme } from '@material-ui/core/styles';
10
+ import { DateTime } from 'luxon';
11
+ import { ResponsiveContainer, ComposedChart, CartesianGrid, XAxis, YAxis, Area, Line, Tooltip, AreaChart } from 'recharts';
12
+ import { f as useScroll, e as useFilters, d as useLastCompleteBillingDate, g as getComparedChange, h as LegendItem, j as formatChange, k as choose, l as CostGrowth, m as useCostOverviewStyles, n as useConfig, o as aggregationSort, p as overviewGraphTickFormatter, q as isInvalid, D as DEFAULT_DATE_FORMAT, r as BarChartTooltip, s as BarChartTooltipItem, t as formatGraphValue, v as getPreviousPeriodTotalCost, w as formatPeriod, x as BarChartLegend, y as useSelectStyles, z as Duration, E as formatLastTwoLookaheadQuarters, H as findAlways, I as useOverviewTabsStyles, J as DefaultNavigation, K as DefaultLoadingAction, M as getResetState, N as getResetStateWithoutInitial } from './index-BFM2mP6Z.esm.js';
13
+ import Typography from '@material-ui/core/Typography';
14
+ import '@material-ui/core/Paper';
15
+ import '@material-ui/core/Slide';
16
+ import '@material-ui/icons/ChevronLeft';
17
+ import '@material-ui/icons/ChevronRight';
18
+ import '@material-ui/core/ButtonBase';
19
+ import 'pluralize';
20
+ import '@material-ui/core/Tooltip';
21
+ import '@material-ui/icons/Lens';
22
+ import '@material-ui/icons/HelpOutlineOutlined';
23
+ import 'classnames';
24
+ import '@material-ui/icons/ArrowDropUp';
25
+ import '@material-ui/icons/ArrowDropDown';
26
+ import { emphasize } from '@material-ui/core/styles/colorManipulator';
27
+ import FullScreenIcon from '@material-ui/icons/Fullscreen';
28
+ import InputLabel from '@material-ui/core/InputLabel';
29
+ import FormControl from '@material-ui/core/FormControl';
30
+ import Select from '@material-ui/core/Select';
31
+ import MenuItem from '@material-ui/core/MenuItem';
32
+
33
+ const ScrollAnchor = ({
34
+ id,
35
+ left = 0,
36
+ top = -20,
37
+ block = "start",
38
+ inline = "nearest",
39
+ behavior = "smooth"
40
+ }) => {
41
+ const divRef = useRef(null);
42
+ const [scroll, setScroll] = useScroll();
43
+ useEffect(() => {
44
+ function scrollIntoView() {
45
+ if (divRef.current && scroll === id) {
46
+ divRef.current.scrollIntoView({
47
+ block,
48
+ inline,
49
+ behavior
50
+ });
51
+ setScroll(null);
52
+ }
53
+ }
54
+ scrollIntoView();
55
+ }, [scroll, setScroll, id, behavior, block, inline]);
56
+ return /* @__PURE__ */ React.createElement(
57
+ "div",
58
+ {
59
+ ref: divRef,
60
+ style: { position: "absolute", height: 0, width: 0, top, left },
61
+ "data-testid": `scroll-test-${id}`
62
+ }
63
+ );
64
+ };
65
+
66
+ function trendFrom(trendline, date) {
67
+ return trendline.slope * (date / 1e3) + trendline.intercept;
68
+ }
69
+ function groupByDate(acc, entry) {
70
+ return { ...acc, [entry.date]: entry.amount };
71
+ }
72
+
73
+ const mapFiltersToProps = ({
74
+ pageFilters,
75
+ setPageFilters
76
+ }) => ({
77
+ ...pageFilters,
78
+ project: pageFilters.project || "all",
79
+ setDuration: (duration) => setPageFilters({
80
+ ...pageFilters,
81
+ duration
82
+ }),
83
+ setProject: (project) => setPageFilters({
84
+ ...pageFilters,
85
+ project: project === "all" ? null : project
86
+ }),
87
+ setMetric: (metric) => setPageFilters({
88
+ ...pageFilters,
89
+ metric
90
+ })
91
+ });
92
+
93
+ const CostOverviewLegend = ({
94
+ dailyCostData,
95
+ metric,
96
+ metricData
97
+ }) => {
98
+ const theme = useTheme();
99
+ const { duration } = useFilters(mapFiltersToProps);
100
+ const lastCompleteBillingDate = useLastCompleteBillingDate();
101
+ const comparedChange = metricData ? getComparedChange(
102
+ dailyCostData,
103
+ metricData,
104
+ duration,
105
+ lastCompleteBillingDate
106
+ ) : null;
107
+ return /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "row" }, dailyCostData.change && /* @__PURE__ */ React.createElement(Box, { mr: 2 }, /* @__PURE__ */ React.createElement(LegendItem, { title: "Cost Trend", markerColor: theme.palette.blue }, formatChange(dailyCostData.change))), metricData && metric && comparedChange && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Box, { mr: 2 }, /* @__PURE__ */ React.createElement(
108
+ LegendItem,
109
+ {
110
+ title: `${metric.name} Trend`,
111
+ markerColor: theme.palette.magenta
112
+ },
113
+ formatChange(metricData.change)
114
+ )), /* @__PURE__ */ React.createElement(
115
+ LegendItem,
116
+ {
117
+ title: choose(["Your Savings", "Your Excess"], comparedChange)
118
+ },
119
+ /* @__PURE__ */ React.createElement(CostGrowth, { change: comparedChange, duration })
120
+ )));
121
+ };
122
+
123
+ const CostOverviewChart = ({
124
+ dailyCostData,
125
+ metric,
126
+ metricData,
127
+ responsive = true
128
+ }) => {
129
+ var _a, _b, _c;
130
+ const theme = useTheme();
131
+ const styles = useCostOverviewStyles(theme);
132
+ const { baseCurrency, hideTrendLine } = useConfig();
133
+ const data = {
134
+ dailyCost: {
135
+ dataKey: "dailyCost",
136
+ name: `Daily Cost`,
137
+ format: "currency",
138
+ data: dailyCostData
139
+ },
140
+ metric: {
141
+ dataKey: (_a = metric == null ? void 0 : metric.kind) != null ? _a : "Unknown",
142
+ name: (_b = metric == null ? void 0 : metric.name) != null ? _b : "Unknown",
143
+ format: (_c = metricData == null ? void 0 : metricData.format) != null ? _c : "number",
144
+ data: metricData
145
+ }
146
+ };
147
+ const metricsByDate = data.metric.data ? data.metric.data.aggregation.reduce(groupByDate, {}) : {};
148
+ const chartData = data.dailyCost.data.aggregation.slice().sort(aggregationSort).map((entry) => ({
149
+ date: Date.parse(entry.date),
150
+ trend: trendFrom(data.dailyCost.data.trendline, Date.parse(entry.date)),
151
+ dailyCost: entry.amount,
152
+ ...metric && data.metric.data ? { [data.metric.dataKey]: metricsByDate[`${entry.date}`] } : {}
153
+ }));
154
+ const tooltipRenderer = ({ label, payload = [] }) => {
155
+ if (isInvalid({ label, payload }))
156
+ return null;
157
+ const dataKeys = [data.dailyCost.dataKey, data.metric.dataKey];
158
+ const date = typeof label === "number" ? DateTime.fromMillis(label) : DateTime.fromISO(label);
159
+ const title = date.toUTC().toFormat(DEFAULT_DATE_FORMAT);
160
+ const formatGraphValueWith = formatGraphValue(baseCurrency);
161
+ const items = payload.filter((p) => dataKeys.includes(p.dataKey)).map((p, i) => ({
162
+ label: p.dataKey === data.dailyCost.dataKey ? data.dailyCost.name : data.metric.name,
163
+ value: p.dataKey === data.dailyCost.dataKey ? formatGraphValueWith(Number(p.value), i, data.dailyCost.format) : formatGraphValueWith(Number(p.value), i, data.metric.format),
164
+ fill: p.dataKey === data.dailyCost.dataKey ? theme.palette.blue : theme.palette.magenta
165
+ }));
166
+ return /* @__PURE__ */ React.createElement(BarChartTooltip, { title }, items.map((item, index) => /* @__PURE__ */ React.createElement(BarChartTooltipItem, { key: `${item.label}-${index}`, item })));
167
+ };
168
+ const localizedTickFormatter = formatGraphValue(baseCurrency);
169
+ return /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "column" }, /* @__PURE__ */ React.createElement(
170
+ CostOverviewLegend,
171
+ {
172
+ dailyCostData,
173
+ metric,
174
+ metricData
175
+ }
176
+ ), /* @__PURE__ */ React.createElement(
177
+ ResponsiveContainer,
178
+ {
179
+ width: responsive ? "100%" : styles.container.width,
180
+ height: styles.container.height,
181
+ className: "cost-overview-chart"
182
+ },
183
+ /* @__PURE__ */ React.createElement(ComposedChart, { margin: styles.chart.margin, data: chartData }, /* @__PURE__ */ React.createElement(CartesianGrid, { stroke: styles.cartesianGrid.stroke }), /* @__PURE__ */ React.createElement(
184
+ XAxis,
185
+ {
186
+ dataKey: "date",
187
+ domain: ["dataMin", "dataMax"],
188
+ tickFormatter: overviewGraphTickFormatter,
189
+ tickCount: 6,
190
+ type: "number",
191
+ stroke: styles.axis.fill
192
+ }
193
+ ), /* @__PURE__ */ React.createElement(
194
+ YAxis,
195
+ {
196
+ domain: [() => 0, "dataMax"],
197
+ tick: { fill: styles.axis.fill },
198
+ tickFormatter: localizedTickFormatter,
199
+ width: styles.yAxis.width,
200
+ yAxisId: data.dailyCost.dataKey
201
+ }
202
+ ), metric && /* @__PURE__ */ React.createElement(
203
+ YAxis,
204
+ {
205
+ domain: ["auto", "auto"],
206
+ width: styles.yAxis.width,
207
+ yAxisId: data.metric.dataKey,
208
+ tickFormatter: (value, index) => localizedTickFormatter(value, index, data.metric.format),
209
+ orientation: "right"
210
+ }
211
+ ), /* @__PURE__ */ React.createElement(
212
+ Area,
213
+ {
214
+ dataKey: data.dailyCost.dataKey,
215
+ isAnimationActive: false,
216
+ fill: theme.palette.blue,
217
+ fillOpacity: 0.4,
218
+ stroke: "none",
219
+ yAxisId: data.dailyCost.dataKey
220
+ }
221
+ ), !hideTrendLine && /* @__PURE__ */ React.createElement(
222
+ Line,
223
+ {
224
+ activeDot: false,
225
+ dataKey: "trend",
226
+ dot: false,
227
+ isAnimationActive: false,
228
+ strokeWidth: 2,
229
+ stroke: theme.palette.blue,
230
+ yAxisId: data.dailyCost.dataKey
231
+ }
232
+ ), metric && /* @__PURE__ */ React.createElement(
233
+ Line,
234
+ {
235
+ dataKey: data.metric.dataKey,
236
+ dot: false,
237
+ isAnimationActive: false,
238
+ strokeWidth: 2,
239
+ stroke: theme.palette.magenta,
240
+ yAxisId: data.metric.dataKey
241
+ }
242
+ ), /* @__PURE__ */ React.createElement(Tooltip, { content: tooltipRenderer, animationDuration: 100 }))
243
+ ));
244
+ };
245
+
246
+ const aggregationSum = (aggregation) => aggregation.reduce((total, curAgg) => total + curAgg.amount, 0);
247
+
248
+ const LOW_COST_THRESHOLD = 0.1;
249
+ const CostOverviewBreakdownChart = ({
250
+ costBreakdown,
251
+ responsive = true
252
+ }) => {
253
+ const theme = useTheme();
254
+ const classes = useCostOverviewStyles(theme);
255
+ const { baseCurrency } = useConfig();
256
+ const lastCompleteBillingDate = useLastCompleteBillingDate();
257
+ const { duration } = useFilters(mapFiltersToProps);
258
+ const [isExpanded, setExpanded] = useState(false);
259
+ if (!costBreakdown) {
260
+ return null;
261
+ }
262
+ const flattenedAggregation = costBreakdown.map((cost) => cost.aggregation).flat();
263
+ const totalCost = aggregationSum(flattenedAggregation);
264
+ const previousPeriodTotal = getPreviousPeriodTotalCost(
265
+ flattenedAggregation,
266
+ duration,
267
+ lastCompleteBillingDate
268
+ );
269
+ const currentPeriodTotal = totalCost - previousPeriodTotal;
270
+ const canExpand = costBreakdown.length >= 8;
271
+ const otherCategoryIds = [];
272
+ const breakdownsByDate = costBreakdown.reduce(
273
+ (breakdownByDate, breakdown) => {
274
+ const breakdownTotal = aggregationSum(breakdown.aggregation);
275
+ const isOtherCategory = canExpand && breakdownTotal < totalCost * LOW_COST_THRESHOLD;
276
+ const updatedBreakdownByDate = { ...breakdownByDate };
277
+ if (isOtherCategory) {
278
+ otherCategoryIds.push(breakdown.id);
279
+ }
280
+ breakdown.aggregation.forEach((curAggregation) => {
281
+ const costsForDate = updatedBreakdownByDate[curAggregation.date] || {};
282
+ updatedBreakdownByDate[curAggregation.date] = {
283
+ ...costsForDate,
284
+ [breakdown.id]: (costsForDate[breakdown.id] || 0) + curAggregation.amount
285
+ };
286
+ });
287
+ return updatedBreakdownByDate;
288
+ },
289
+ {}
290
+ );
291
+ const chartData = Object.keys(breakdownsByDate).map((date) => {
292
+ const costsForDate = Object.keys(breakdownsByDate[date]).reduce(
293
+ (dateCosts, breakdown) => {
294
+ const cost = breakdownsByDate[date][breakdown];
295
+ const breakdownCost = !isExpanded && otherCategoryIds.includes(breakdown) ? { Other: (dateCosts.Other || 0) + cost } : { [breakdown]: cost };
296
+ return { ...dateCosts, ...breakdownCost };
297
+ },
298
+ {}
299
+ );
300
+ return {
301
+ ...costsForDate,
302
+ date: Date.parse(date)
303
+ };
304
+ }).sort((a, b) => a.date - b.date);
305
+ const sortedBreakdowns = costBreakdown.sort(
306
+ (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation)
307
+ );
308
+ const renderAreas = () => {
309
+ const separatedBreakdowns = sortedBreakdowns.filter(
310
+ (breakdown) => breakdown.id !== "Other" && !otherCategoryIds.includes(breakdown.id)
311
+ ).map((breakdown) => breakdown.id);
312
+ const breakdownsToDisplay = isExpanded ? sortedBreakdowns.map((breakdown) => breakdown.id) : ["Other", ...separatedBreakdowns];
313
+ return breakdownsToDisplay.map((breakdown, i) => {
314
+ const color = theme.palette.dataViz[(breakdownsToDisplay.length - 1 - i) % (theme.palette.dataViz.length - 1)];
315
+ return /* @__PURE__ */ React.createElement(
316
+ Area,
317
+ {
318
+ key: breakdown,
319
+ dataKey: breakdown,
320
+ isAnimationActive: false,
321
+ stackId: "1",
322
+ stroke: color,
323
+ fill: color,
324
+ onClick: () => setExpanded(true),
325
+ style: {
326
+ cursor: breakdown === "Other" && !isExpanded ? "pointer" : void 0
327
+ }
328
+ }
329
+ );
330
+ });
331
+ };
332
+ const tooltipRenderer = ({ label, payload = [] }) => {
333
+ if (isInvalid({ label, payload }))
334
+ return null;
335
+ const date = typeof label === "number" ? DateTime.fromMillis(label) : DateTime.fromISO(label);
336
+ const dateTitle = date.toUTC().toFormat(DEFAULT_DATE_FORMAT);
337
+ const formatGraphValueWith = formatGraphValue(baseCurrency);
338
+ const items = payload.map((p, i) => ({
339
+ label: p.dataKey,
340
+ value: formatGraphValueWith(Number(p.value), i),
341
+ fill: p.color
342
+ }));
343
+ const expandText = /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
344
+ Divider,
345
+ {
346
+ style: {
347
+ backgroundColor: emphasize(theme.palette.divider, 1),
348
+ margin: "10px 0"
349
+ }
350
+ }
351
+ ), /* @__PURE__ */ React.createElement(Box, { display: "flex", justifyContent: "space-between", alignItems: "center" }, /* @__PURE__ */ React.createElement(FullScreenIcon, null), /* @__PURE__ */ React.createElement(Typography, null, "Click to expand")));
352
+ return /* @__PURE__ */ React.createElement(BarChartTooltip, { title: dateTitle }, items.reverse().map((item, index) => /* @__PURE__ */ React.createElement(BarChartTooltipItem, { key: `${item.label}-${index}`, item })), canExpand && !isExpanded ? expandText : null);
353
+ };
354
+ const options = {
355
+ previousName: formatPeriod(duration, lastCompleteBillingDate, false),
356
+ currentName: formatPeriod(duration, lastCompleteBillingDate, true),
357
+ hideMarker: true
358
+ };
359
+ return /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { display: "flex", flexDirection: "row" }, /* @__PURE__ */ React.createElement(
360
+ BarChartLegend,
361
+ {
362
+ costStart: previousPeriodTotal,
363
+ costEnd: currentPeriodTotal,
364
+ options
365
+ }
366
+ )), /* @__PURE__ */ React.createElement(
367
+ ResponsiveContainer,
368
+ {
369
+ width: responsive ? "100%" : classes.container.width,
370
+ height: classes.container.height
371
+ },
372
+ /* @__PURE__ */ React.createElement(
373
+ AreaChart,
374
+ {
375
+ data: chartData,
376
+ margin: {
377
+ top: 16,
378
+ right: 30,
379
+ bottom: 40
380
+ }
381
+ },
382
+ /* @__PURE__ */ React.createElement(CartesianGrid, { stroke: classes.cartesianGrid.stroke }),
383
+ /* @__PURE__ */ React.createElement(
384
+ XAxis,
385
+ {
386
+ dataKey: "date",
387
+ domain: ["dataMin", "dataMax"],
388
+ tickFormatter: overviewGraphTickFormatter,
389
+ tickCount: 6,
390
+ type: "number",
391
+ stroke: classes.axis.fill
392
+ }
393
+ ),
394
+ /* @__PURE__ */ React.createElement(
395
+ YAxis,
396
+ {
397
+ domain: [() => 0, "dataMax"],
398
+ tick: { fill: classes.axis.fill },
399
+ tickFormatter: formatGraphValue(baseCurrency),
400
+ width: classes.yAxis.width
401
+ }
402
+ ),
403
+ renderAreas(),
404
+ /* @__PURE__ */ React.createElement(Tooltip, { content: tooltipRenderer, animationDuration: 100 })
405
+ )
406
+ ));
407
+ };
408
+
409
+ const CostOverviewHeader = ({
410
+ title,
411
+ subtitle,
412
+ children
413
+ }) => /* @__PURE__ */ React.createElement(
414
+ Box,
415
+ {
416
+ mt: 2,
417
+ ml: 1,
418
+ mb: 1,
419
+ display: "flex",
420
+ flexDirection: "row",
421
+ justifyContent: "space-between",
422
+ alignItems: "center"
423
+ },
424
+ /* @__PURE__ */ React.createElement(Box, { minHeight: 40, paddingRight: 5 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5", gutterBottom: true }, title), !!subtitle && /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle2", color: "textSecondary", component: "div" }, subtitle)),
425
+ /* @__PURE__ */ React.createElement(Box, { minHeight: 40, maxHeight: 60, display: "flex" }, children)
426
+ );
427
+
428
+ const MetricSelect = ({
429
+ metric,
430
+ metrics,
431
+ onSelect
432
+ }) => {
433
+ const classes = useSelectStyles();
434
+ function onChange(e) {
435
+ if (e.target.value === "none") {
436
+ onSelect(null);
437
+ } else {
438
+ onSelect(e.target.value);
439
+ }
440
+ }
441
+ return /* @__PURE__ */ React.createElement(FormControl, { variant: "outlined" }, /* @__PURE__ */ React.createElement(InputLabel, { shrink: true, id: "metric-select-label" }, "Compare to:"), /* @__PURE__ */ React.createElement(
442
+ Select,
443
+ {
444
+ id: "metric-select",
445
+ labelId: "metric-select-label",
446
+ labelWidth: 100,
447
+ className: classes.select,
448
+ value: metric != null ? metric : "none",
449
+ onChange
450
+ },
451
+ /* @__PURE__ */ React.createElement(MenuItem, { className: classes.menuItem, key: "none", value: "none" }, /* @__PURE__ */ React.createElement("em", null, "None")),
452
+ metrics.map((m) => /* @__PURE__ */ React.createElement(MenuItem, { className: classes.menuItem, key: m.kind, value: m.kind }, /* @__PURE__ */ React.createElement("b", null, m.name)))
453
+ ));
454
+ };
455
+
456
+ function getDefaultOptions(lastCompleteBillingDate) {
457
+ return [
458
+ {
459
+ value: Duration.P90D,
460
+ label: "Past 6 Months"
461
+ },
462
+ {
463
+ value: Duration.P30D,
464
+ label: "Past 60 Days"
465
+ },
466
+ {
467
+ value: Duration.P3M,
468
+ label: formatLastTwoLookaheadQuarters(lastCompleteBillingDate)
469
+ }
470
+ ];
471
+ }
472
+ const PeriodSelect = ({
473
+ duration,
474
+ onSelect,
475
+ options
476
+ }) => {
477
+ const classes = useSelectStyles();
478
+ const lastCompleteBillingDate = useLastCompleteBillingDate();
479
+ const optionsOrDefault = options != null ? options : getDefaultOptions(lastCompleteBillingDate);
480
+ const handleOnChange = (e) => {
481
+ onSelect(e.target.value);
482
+ };
483
+ const renderValue = (value) => {
484
+ const option = findAlways(optionsOrDefault, (o) => o.value === value);
485
+ return /* @__PURE__ */ React.createElement("b", null, option.label);
486
+ };
487
+ return /* @__PURE__ */ React.createElement(
488
+ Select,
489
+ {
490
+ className: classes.select,
491
+ variant: "outlined",
492
+ onChange: handleOnChange,
493
+ value: duration,
494
+ renderValue,
495
+ "data-testid": "period-select"
496
+ },
497
+ optionsOrDefault.map((option) => /* @__PURE__ */ React.createElement(
498
+ MenuItem,
499
+ {
500
+ className: classes.menuItem,
501
+ key: option.value,
502
+ value: option.value,
503
+ "data-testid": `period-select-option-${option.value}`
504
+ },
505
+ option.label
506
+ ))
507
+ );
508
+ };
509
+
510
+ const CostOverviewCard = ({
511
+ dailyCostData,
512
+ metricData
513
+ }) => {
514
+ var _a;
515
+ const theme = useTheme();
516
+ const styles = useOverviewTabsStyles(theme);
517
+ const config = useConfig();
518
+ const [tabIndex, setTabIndex] = useState(0);
519
+ const { setDuration, setProject, setMetric, ...filters } = useFilters(mapFiltersToProps);
520
+ useEffect(() => {
521
+ var _a2;
522
+ const lastIndex = Object.keys((_a2 = dailyCostData.groupedCosts) != null ? _a2 : {}).length;
523
+ if (tabIndex > lastIndex) {
524
+ setTabIndex(0);
525
+ }
526
+ }, [dailyCostData, tabIndex, setTabIndex]);
527
+ const metric = filters.metric ? findAlways(config.metrics, (m) => m.kind === filters.metric) : null;
528
+ const breakdownTabs = Object.keys((_a = dailyCostData.groupedCosts) != null ? _a : {}).map(
529
+ (key) => ({
530
+ id: key,
531
+ label: `Breakdown by ${key}`,
532
+ title: `Cloud Cost By ${capitalize(key)}`
533
+ })
534
+ );
535
+ const tabs = [
536
+ { id: "overview", label: "Total cost", title: "Cloud Cost" }
537
+ ].concat(breakdownTabs);
538
+ const safeTabIndex = tabIndex > tabs.length - 1 ? 0 : tabIndex;
539
+ const OverviewTabs = () => {
540
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
541
+ Tabs,
542
+ {
543
+ indicatorColor: "primary",
544
+ onChange: (_, index) => setTabIndex(index),
545
+ value: safeTabIndex
546
+ },
547
+ tabs.map((tab, index) => /* @__PURE__ */ React.createElement(
548
+ Tab,
549
+ {
550
+ className: styles.default,
551
+ label: tab.label,
552
+ key: tab.id,
553
+ value: index,
554
+ classes: { selected: styles.selected }
555
+ }
556
+ ))
557
+ ));
558
+ };
559
+ const showMetricSelect = metricData && config.metrics.length && safeTabIndex === 0;
560
+ return /* @__PURE__ */ React.createElement(Card, { style: { position: "relative", overflow: "visible" } }, /* @__PURE__ */ React.createElement(ScrollAnchor, { id: DefaultNavigation.CostOverviewCard }), /* @__PURE__ */ React.createElement(CardContent, null, dailyCostData.groupedCosts && /* @__PURE__ */ React.createElement(OverviewTabs, null), /* @__PURE__ */ React.createElement(CostOverviewHeader, { title: tabs[safeTabIndex].title }, /* @__PURE__ */ React.createElement(PeriodSelect, { duration: filters.duration, onSelect: setDuration })), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(Box, { ml: 2, my: 1, display: "flex", flexDirection: "column" }, safeTabIndex === 0 ? /* @__PURE__ */ React.createElement(
561
+ CostOverviewChart,
562
+ {
563
+ dailyCostData,
564
+ metric,
565
+ metricData
566
+ }
567
+ ) : /* @__PURE__ */ React.createElement(
568
+ CostOverviewBreakdownChart,
569
+ {
570
+ costBreakdown: dailyCostData.groupedCosts[tabs[safeTabIndex].id]
571
+ }
572
+ )), /* @__PURE__ */ React.createElement(Box, { display: "flex", justifyContent: "flex-end", alignItems: "center" }, showMetricSelect && /* @__PURE__ */ React.createElement(
573
+ MetricSelect,
574
+ {
575
+ metric: filters.metric,
576
+ metrics: config.metrics,
577
+ onSelect: setMetric
578
+ }
579
+ ))));
580
+ };
581
+
582
+ const mapLoadingToProps = ({ state, actions, dispatch }) => ({
583
+ loadingActions: actions,
584
+ loadingGroups: state[DefaultLoadingAction.UserGroups],
585
+ loadingBillingDate: state[DefaultLoadingAction.LastCompleteBillingDate],
586
+ loadingInitial: state[DefaultLoadingAction.CostInsightsInitial],
587
+ dispatchInitial: (isLoading) => dispatch({ [DefaultLoadingAction.CostInsightsInitial]: isLoading }),
588
+ dispatchInsights: (isLoading) => dispatch({ [DefaultLoadingAction.CostInsightsPage]: isLoading }),
589
+ dispatchNone: (loadingActions) => dispatch(getResetState(loadingActions)),
590
+ dispatchReset: (loadingActions) => dispatch(getResetStateWithoutInitial(loadingActions))
591
+ });
592
+
593
+ export { CostOverviewCard as C, PeriodSelect as P, ScrollAnchor as S, mapLoadingToProps as m };
594
+ //# sourceMappingURL=selector-D7iXJwd9.esm.js.map