@red-hat-developer-hub/plugin-cost-management 2.0.3-rc.4

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 (97) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +152 -0
  3. package/app-config.dynamic.yaml +28 -0
  4. package/dist/Router.esm.js +21 -0
  5. package/dist/Router.esm.js.map +1 -0
  6. package/dist/apis.esm.js +14 -0
  7. package/dist/apis.esm.js.map +1 -0
  8. package/dist/components/BasePage.esm.js +29 -0
  9. package/dist/components/BasePage.esm.js.map +1 -0
  10. package/dist/components/InfoCardTitle.esm.js +9 -0
  11. package/dist/components/InfoCardTitle.esm.js.map +1 -0
  12. package/dist/components/PageLayout.esm.js +56 -0
  13. package/dist/components/PageLayout.esm.js.map +1 -0
  14. package/dist/components/filtersStyles.esm.js +32 -0
  15. package/dist/components/filtersStyles.esm.js.map +1 -0
  16. package/dist/components/icon/AnalyticsIcon.esm.js +11 -0
  17. package/dist/components/icon/AnalyticsIcon.esm.js.map +1 -0
  18. package/dist/components/icon/AnalyticsIconFilled.esm.js +9 -0
  19. package/dist/components/icon/AnalyticsIconFilled.esm.js.map +1 -0
  20. package/dist/components/icon/AnalyticsIconOutlined.esm.js +9 -0
  21. package/dist/components/icon/AnalyticsIconOutlined.esm.js.map +1 -0
  22. package/dist/components/icon/CostManagementIcon.esm.js +11 -0
  23. package/dist/components/icon/CostManagementIcon.esm.js.map +1 -0
  24. package/dist/components/icon/CostManagementIconFilled.esm.js +9 -0
  25. package/dist/components/icon/CostManagementIconFilled.esm.js.map +1 -0
  26. package/dist/components/icon/CostManagementIconOutlined.esm.js +9 -0
  27. package/dist/components/icon/CostManagementIconOutlined.esm.js.map +1 -0
  28. package/dist/constants/currencies.esm.js +36 -0
  29. package/dist/constants/currencies.esm.js.map +1 -0
  30. package/dist/hooks/usePatternFlyTheme.esm.js +22 -0
  31. package/dist/hooks/usePatternFlyTheme.esm.js.map +1 -0
  32. package/dist/hooks/useThemeBackgroundColor.esm.js +13 -0
  33. package/dist/hooks/useThemeBackgroundColor.esm.js.map +1 -0
  34. package/dist/index.d.ts +71 -0
  35. package/dist/index.esm.js +9 -0
  36. package/dist/index.esm.js.map +1 -0
  37. package/dist/pages/openshift/OpenShiftPage.esm.js +727 -0
  38. package/dist/pages/openshift/OpenShiftPage.esm.js.map +1 -0
  39. package/dist/pages/openshift/components/AutocompleteComponent.esm.js +65 -0
  40. package/dist/pages/openshift/components/AutocompleteComponent.esm.js.map +1 -0
  41. package/dist/pages/openshift/components/DownloadIconButton.esm.js +49 -0
  42. package/dist/pages/openshift/components/DownloadIconButton.esm.js.map +1 -0
  43. package/dist/pages/openshift/components/Filters.esm.js +360 -0
  44. package/dist/pages/openshift/components/Filters.esm.js.map +1 -0
  45. package/dist/pages/openshift/components/PageHeader.esm.js +31 -0
  46. package/dist/pages/openshift/components/PageHeader.esm.js.map +1 -0
  47. package/dist/pages/openshift/components/SelectComponent.esm.js +68 -0
  48. package/dist/pages/openshift/components/SelectComponent.esm.js.map +1 -0
  49. package/dist/pages/openshift/components/TableToolbar.esm.js +249 -0
  50. package/dist/pages/openshift/components/TableToolbar.esm.js.map +1 -0
  51. package/dist/pages/optimizations/OptimizationsPage.esm.js +205 -0
  52. package/dist/pages/optimizations/OptimizationsPage.esm.js.map +1 -0
  53. package/dist/pages/optimizations/components/ComboBox.esm.js +84 -0
  54. package/dist/pages/optimizations/components/ComboBox.esm.js.map +1 -0
  55. package/dist/pages/optimizations/components/Filters.esm.js +59 -0
  56. package/dist/pages/optimizations/components/Filters.esm.js.map +1 -0
  57. package/dist/pages/optimizations/components/TableToolbar.esm.js +40 -0
  58. package/dist/pages/optimizations/components/TableToolbar.esm.js.map +1 -0
  59. package/dist/pages/optimizations-breakdown/OptimizationsBreakdownPage.esm.js +211 -0
  60. package/dist/pages/optimizations-breakdown/OptimizationsBreakdownPage.esm.js.map +1 -0
  61. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/OptimizationEngineTab.esm.js +132 -0
  62. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/OptimizationEngineTab.esm.js.map +1 -0
  63. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/CodeInfoCard.esm.js +37 -0
  64. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/CodeInfoCard.esm.js.map +1 -0
  65. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/ContainerInfoCard.esm.js +50 -0
  66. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/ContainerInfoCard.esm.js.map +1 -0
  67. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/ChartInfoCard.esm.js +26 -0
  68. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/ChartInfoCard.esm.js.map +1 -0
  69. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/IntlBreakdownChart.esm.js +51 -0
  70. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/IntlBreakdownChart.esm.js.map +1 -0
  71. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/OptimizationsBreakdownChart.esm.js +388 -0
  72. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/OptimizationsBreakdownChart.esm.js.map +1 -0
  73. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/i18n/data.json.esm.js +512 -0
  74. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/i18n/data.json.esm.js.map +1 -0
  75. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/i18n/intl.esm.js +18 -0
  76. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/i18n/intl.esm.js.map +1 -0
  77. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/i18n/messages.esm.js +72 -0
  78. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/i18n/messages.esm.js.map +1 -0
  79. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/utils/chart-data-format.esm.js +96 -0
  80. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/utils/chart-data-format.esm.js.map +1 -0
  81. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/utils/chart-datum.esm.js +74 -0
  82. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/utils/chart-datum.esm.js.map +1 -0
  83. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/utils/chart-utils.esm.js +109 -0
  84. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/utils/chart-utils.esm.js.map +1 -0
  85. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/utils/format.esm.js +25 -0
  86. package/dist/pages/optimizations-breakdown/components/optimization-engine-tab/components/chart-info-card/components/intl-breakdown-chart/components/optimizations-breakdown-chart/components/optimizations-breakdown-chart/utils/format.esm.js.map +1 -0
  87. package/dist/pages/optimizations-breakdown/models/ChartEnums.esm.js +23 -0
  88. package/dist/pages/optimizations-breakdown/models/ChartEnums.esm.js.map +1 -0
  89. package/dist/pages/optimizations-breakdown/models/YamlCodeData.esm.js +129 -0
  90. package/dist/pages/optimizations-breakdown/models/YamlCodeData.esm.js.map +1 -0
  91. package/dist/plugin.esm.js +73 -0
  92. package/dist/plugin.esm.js.map +1 -0
  93. package/dist/routes.esm.js +16 -0
  94. package/dist/routes.esm.js.map +1 -0
  95. package/dist/utils/dates.esm.js +37 -0
  96. package/dist/utils/dates.esm.js.map +1 -0
  97. package/package.json +110 -0
@@ -0,0 +1,727 @@
1
+ import React, { useState, useRef, useMemo, useCallback } from 'react';
2
+ import { ResponseErrorPanel, InfoCard, Table } from '@backstage/core-components';
3
+ import Typography from '@material-ui/core/Typography';
4
+ import { BasePage } from '../../components/BasePage.esm.js';
5
+ import { PageLayout } from '../../components/PageLayout.esm.js';
6
+ import { Filters } from './components/Filters.esm.js';
7
+ import { useTheme, Divider } from '@material-ui/core';
8
+ import { PageHeader } from './components/PageHeader.esm.js';
9
+ import { TableToolbar } from './components/TableToolbar.esm.js';
10
+ import { useApi } from '@backstage/core-plugin-api';
11
+ import { costManagementSlimApiRef } from '../../apis.esm.js';
12
+ import useAsync from 'react-use/lib/useAsync';
13
+ import { CURRENCY_SYMBOLS } from '../../constants/currencies.esm.js';
14
+ import { DownloadIconButton } from './components/DownloadIconButton.esm.js';
15
+
16
+ const formatCurrency = (value, currencyCode) => {
17
+ const symbol = CURRENCY_SYMBOLS[currencyCode] || currencyCode;
18
+ return `${symbol}${value.toLocaleString("en-US", {
19
+ minimumFractionDigits: 2,
20
+ maximumFractionDigits: 2
21
+ })}`;
22
+ };
23
+ function getDateRangeText(timeRange) {
24
+ const now = /* @__PURE__ */ new Date();
25
+ const currentMonth = now.getMonth();
26
+ const currentYear = now.getFullYear();
27
+ const currentDay = now.getDate();
28
+ if (timeRange === "month-to-date") {
29
+ const monthName2 = now.toLocaleString("en-US", { month: "long" });
30
+ return `${monthName2} 1-${currentDay}`;
31
+ }
32
+ const prevMonth = currentMonth === 0 ? 11 : currentMonth - 1;
33
+ const prevYear = currentMonth === 0 ? currentYear - 1 : currentYear;
34
+ const prevMonthDate = new Date(prevYear, prevMonth + 1, 0);
35
+ const lastDay = prevMonthDate.getDate();
36
+ const monthName = prevMonthDate.toLocaleString("en-US", { month: "long" });
37
+ return `${monthName} 1-${lastDay}`;
38
+ }
39
+ function buildCostManagementQueryParams(config, options = {}) {
40
+ const {
41
+ groupBy,
42
+ selectedTag,
43
+ overheadDistribution,
44
+ timeRange,
45
+ currency,
46
+ showPlatformSum,
47
+ filterBy,
48
+ filterValue,
49
+ filterOperation,
50
+ selectedTagKey,
51
+ selectedTagValue,
52
+ sortField,
53
+ sortDirection
54
+ } = config;
55
+ const { limit, offset, specificItemName } = options;
56
+ const groupByParam = groupBy === "tag" ? `group_by[tag:${selectedTag}]` : `group_by[${groupBy}]`;
57
+ const deltaParam = groupBy === "project" && overheadDistribution === "distribute" ? "distributed_cost" : "cost";
58
+ const timeScopeValue = timeRange === "month-to-date" ? -1 : -2;
59
+ const queryParams = {
60
+ currency,
61
+ delta: deltaParam,
62
+ "filter[resolution]": "monthly",
63
+ "filter[time_scope_units]": "month",
64
+ "filter[time_scope_value]": timeScopeValue
65
+ };
66
+ if (limit !== void 0) {
67
+ queryParams["filter[limit]"] = limit;
68
+ }
69
+ if (offset !== void 0) {
70
+ queryParams["filter[offset]"] = offset;
71
+ }
72
+ if (showPlatformSum) {
73
+ queryParams.category = "Platform";
74
+ }
75
+ queryParams[groupByParam] = "*";
76
+ if (specificItemName) {
77
+ queryParams[`filter[${groupBy}]`] = specificItemName;
78
+ } else if (filterBy === "tag" && selectedTagKey && selectedTagValue) {
79
+ if (filterOperation === "exact") {
80
+ queryParams[`filter[exact:tag:${selectedTagKey}]`] = selectedTagValue;
81
+ } else if (filterOperation === "excludes") {
82
+ queryParams[`exclude[tag:${selectedTagKey}]`] = selectedTagValue;
83
+ } else {
84
+ queryParams[`filter[tag:${selectedTagKey}]`] = selectedTagValue;
85
+ }
86
+ } else if (filterValue) {
87
+ if (filterOperation === "exact") {
88
+ queryParams[`filter[exact:${filterBy}]`] = filterValue;
89
+ } else if (filterOperation === "excludes") {
90
+ queryParams[`exclude[${filterBy}]`] = filterValue;
91
+ } else {
92
+ queryParams[`filter[${filterBy}]`] = filterValue;
93
+ }
94
+ }
95
+ let apiSortField;
96
+ if (sortField) {
97
+ if (sortField === "cost") {
98
+ apiSortField = deltaParam;
99
+ } else if (sortField === "projectName") {
100
+ apiSortField = groupBy;
101
+ } else {
102
+ apiSortField = deltaParam;
103
+ }
104
+ queryParams[`order_by[${apiSortField}]`] = sortDirection;
105
+ } else {
106
+ queryParams[`order_by[${deltaParam}]`] = "desc";
107
+ }
108
+ return queryParams;
109
+ }
110
+ function OpenShiftPage() {
111
+ const api = useApi(costManagementSlimApiRef);
112
+ const [groupBy, setGroupBy] = useState("project");
113
+ const [overheadDistribution, setOverheadDistribution] = useState("distribute");
114
+ const [timeRange, setTimeRange] = useState("month-to-date");
115
+ const [currency, setCurrency] = useState("USD");
116
+ const [filterBy, setFilterBy] = useState("project");
117
+ const [filterOperation, setFilterOperation] = useState("includes");
118
+ const [filterValue, setFilterValue] = useState("");
119
+ const [showPlatformSum, setShowPlatformSum] = useState(false);
120
+ const [selectedRows, setSelectedRows] = useState(/* @__PURE__ */ new Set());
121
+ const [currentPage, setCurrentPage] = useState(0);
122
+ const [pageSize, setPageSize] = useState(5);
123
+ const [sortField, setSortField] = useState(null);
124
+ const [sortDirection, setSortDirection] = useState("desc");
125
+ const [tags, setTags] = useState([]);
126
+ const [selectedTag, setSelectedTag] = useState("");
127
+ const [selectedTagKey, setSelectedTagKey] = useState("");
128
+ const [selectedTagValue, setSelectedTagValue] = useState("");
129
+ const [showMonthOverMonthChange, setShowMonthOverMonthChange] = useState(true);
130
+ const [showInfrastructureCost, setShowInfrastructureCost] = useState(false);
131
+ const [showSupplementaryCost, setShowSupplementaryCost] = useState(false);
132
+ const [isDownloading, setIsDownloading] = useState(false);
133
+ const theme = useTheme();
134
+ const isDarkMode = theme.palette.mode === "dark";
135
+ const displayDataRef = useRef(null);
136
+ useAsync(async () => {
137
+ try {
138
+ const timeScopeValue = timeRange === "month-to-date" ? -1 : -2;
139
+ const response = await api.getOpenShiftTags(timeScopeValue);
140
+ const tagsData = await response.json();
141
+ setTags(tagsData.data || []);
142
+ } catch {
143
+ setTags([]);
144
+ }
145
+ }, [api, timeRange]);
146
+ const queryParamsConfig = useMemo(
147
+ () => ({
148
+ groupBy,
149
+ selectedTag,
150
+ overheadDistribution,
151
+ timeRange,
152
+ currency,
153
+ showPlatformSum,
154
+ filterBy,
155
+ filterValue,
156
+ filterOperation,
157
+ selectedTagKey,
158
+ selectedTagValue,
159
+ sortField,
160
+ sortDirection
161
+ }),
162
+ [
163
+ groupBy,
164
+ selectedTag,
165
+ overheadDistribution,
166
+ timeRange,
167
+ currency,
168
+ showPlatformSum,
169
+ filterBy,
170
+ filterValue,
171
+ filterOperation,
172
+ selectedTagKey,
173
+ selectedTagValue,
174
+ sortField,
175
+ sortDirection
176
+ ]
177
+ );
178
+ const {
179
+ value: costData,
180
+ loading,
181
+ error
182
+ } = useAsync(async () => {
183
+ if (groupBy === "tag" && !selectedTag) {
184
+ return null;
185
+ }
186
+ const offset = currentPage * pageSize;
187
+ const queryParams = buildCostManagementQueryParams(queryParamsConfig, {
188
+ limit: pageSize,
189
+ offset
190
+ });
191
+ const response = await api.getCostManagementReport({
192
+ query: queryParams
193
+ });
194
+ return response.json();
195
+ }, [
196
+ currency,
197
+ overheadDistribution,
198
+ timeRange,
199
+ groupBy,
200
+ filterBy,
201
+ filterValue,
202
+ filterOperation,
203
+ showPlatformSum,
204
+ api,
205
+ currentPage,
206
+ pageSize,
207
+ sortField,
208
+ sortDirection,
209
+ selectedTag,
210
+ selectedTagKey,
211
+ selectedTagValue
212
+ ]);
213
+ const displayData = useMemo(() => {
214
+ if (!costData) {
215
+ const today = /* @__PURE__ */ new Date();
216
+ const month2 = timeRange === "previous-month" ? new Date(
217
+ today.getFullYear(),
218
+ today.getMonth() - 1,
219
+ 1
220
+ ).toLocaleString("en-US", { month: "long" }) : today.toLocaleString("en-US", { month: "long" });
221
+ const endDate2 = timeRange === "previous-month" ? new Date(today.getFullYear(), today.getMonth(), 0).getDate().toString() : today.getDate().toString();
222
+ return {
223
+ totalCost: 0,
224
+ month: month2,
225
+ endDate: endDate2,
226
+ currencyCode: currency,
227
+ projects: []
228
+ };
229
+ }
230
+ const arrayKey = `${groupBy}s`;
231
+ const groupedArray = costData.data?.[0]?.[arrayKey];
232
+ const projects = groupedArray?.map((item, index) => {
233
+ const value = item.values?.[0] || {};
234
+ let itemName;
235
+ let clusterId;
236
+ if (groupBy === "cluster") {
237
+ const valueItem = item.values?.[0];
238
+ const clustersArray = valueItem?.clusters;
239
+ itemName = clustersArray && clustersArray.length > 0 ? clustersArray[0] : item.cluster || "Unknown";
240
+ clusterId = item.cluster || void 0;
241
+ } else {
242
+ const nameField = groupBy;
243
+ itemName = item[nameField] || "Unknown";
244
+ }
245
+ const costField = groupBy === "project" && overheadDistribution === "distribute" ? "distributed" : "total";
246
+ const costValue = value?.cost?.[costField]?.value || 0;
247
+ const deltaPercent = value?.delta_percent || 0;
248
+ const deltaValue = value?.delta_value || 0;
249
+ const infrastructureValue = value?.infrastructure?.total?.value || 0;
250
+ const supplementaryValue = value?.supplementary?.total?.value || 0;
251
+ return {
252
+ id: `${index}`,
253
+ projectName: itemName,
254
+ clusterId,
255
+ cost: costValue,
256
+ costPercentage: 0,
257
+ monthOverMonthChange: deltaPercent,
258
+ monthOverMonthValue: Math.abs(deltaValue),
259
+ includesOverhead: value?.cost?.network_unattributed_distributed?.value !== 0 || value?.cost?.worker_unallocated_distributed?.value !== 0 || value?.cost?.platform_distributed?.value !== 0 || value?.cost?.storage_unattributed_distributed?.value !== 0,
260
+ previousPeriodCost: costValue + Math.abs(deltaValue),
261
+ infrastructureCost: infrastructureValue,
262
+ infrastructureCostPercentage: 0,
263
+ supplementaryCost: supplementaryValue,
264
+ supplementaryCostPercentage: 0
265
+ };
266
+ }) || [];
267
+ const totalCost = costData.meta?.total?.cost?.total?.value || 0;
268
+ const totalInfrastructureCost = costData.meta?.total?.infrastructure?.total?.value || 0;
269
+ const totalSupplementaryCost = costData.meta?.total?.supplementary?.total?.value || 0;
270
+ const currencyCode = costData.meta?.currency || currency;
271
+ const projectsWithPercentage = projects.map((p) => ({
272
+ ...p,
273
+ costPercentage: totalCost > 0 ? p.cost / totalCost * 100 : 0,
274
+ infrastructureCostPercentage: totalInfrastructureCost > 0 ? p.infrastructureCost / totalInfrastructureCost * 100 : 0,
275
+ supplementaryCostPercentage: totalSupplementaryCost > 0 ? p.supplementaryCost / totalSupplementaryCost * 100 : 0
276
+ }));
277
+ let month;
278
+ let endDate;
279
+ if (timeRange === "previous-month") {
280
+ const today = /* @__PURE__ */ new Date();
281
+ const previousMonth = new Date(
282
+ today.getFullYear(),
283
+ today.getMonth() - 1,
284
+ 1
285
+ );
286
+ month = previousMonth.toLocaleString("en-US", { month: "long" });
287
+ const lastDayOfPreviousMonth = new Date(
288
+ today.getFullYear(),
289
+ today.getMonth(),
290
+ 0
291
+ ).getDate();
292
+ endDate = lastDayOfPreviousMonth.toString();
293
+ } else {
294
+ const today = /* @__PURE__ */ new Date();
295
+ month = today.toLocaleString("en-US", { month: "long" });
296
+ endDate = today.getDate().toString();
297
+ }
298
+ return {
299
+ totalCost,
300
+ month,
301
+ endDate,
302
+ currencyCode,
303
+ projects: projectsWithPercentage
304
+ };
305
+ }, [costData, currency, groupBy, overheadDistribution, timeRange]);
306
+ displayDataRef.current = displayData;
307
+ const buildDownloadQueryParams = useCallback(
308
+ (specificItemName) => {
309
+ return buildCostManagementQueryParams(queryParamsConfig, {
310
+ specificItemName
311
+ });
312
+ },
313
+ [queryParamsConfig]
314
+ );
315
+ const handleHeaderDownload = useCallback(
316
+ async (format) => {
317
+ if (isDownloading) return;
318
+ setIsDownloading(true);
319
+ try {
320
+ const queryParams = buildDownloadQueryParams();
321
+ await api.downloadCostManagementReport({
322
+ query: queryParams,
323
+ format
324
+ });
325
+ } catch (err) {
326
+ console.error(`Failed to download ${format}:`, err);
327
+ } finally {
328
+ setIsDownloading(false);
329
+ }
330
+ },
331
+ [api, buildDownloadQueryParams, isDownloading]
332
+ );
333
+ const handleRowDownload = useCallback(
334
+ async (itemName, format) => {
335
+ if (isDownloading) return;
336
+ setIsDownloading(true);
337
+ try {
338
+ const queryParams = buildDownloadQueryParams(itemName);
339
+ await api.downloadCostManagementReport({
340
+ query: queryParams,
341
+ format
342
+ });
343
+ } catch (err) {
344
+ console.error(`Failed to download ${format} for ${itemName}:`, err);
345
+ } finally {
346
+ setIsDownloading(false);
347
+ }
348
+ },
349
+ [api, buildDownloadQueryParams, isDownloading]
350
+ );
351
+ const handleRowSelect = useCallback(
352
+ (rowId, isSelected) => {
353
+ const newSelectedRows = new Set(selectedRows);
354
+ if (isSelected) {
355
+ newSelectedRows.add(rowId);
356
+ } else {
357
+ newSelectedRows.delete(rowId);
358
+ }
359
+ setSelectedRows(newSelectedRows);
360
+ },
361
+ [selectedRows]
362
+ );
363
+ const handleSelectAll = useCallback(
364
+ (isSelected) => {
365
+ if (isSelected && displayData) {
366
+ setSelectedRows(
367
+ new Set(displayData.projects.map((project) => project.id))
368
+ );
369
+ } else {
370
+ setSelectedRows(/* @__PURE__ */ new Set());
371
+ }
372
+ },
373
+ [displayData]
374
+ );
375
+ const handlePageChange = useCallback((page, newPageSize) => {
376
+ setCurrentPage(page);
377
+ setPageSize(newPageSize);
378
+ }, []);
379
+ const handleRowsPerPageChange = useCallback((newPageSize) => {
380
+ setPageSize(newPageSize);
381
+ setCurrentPage(0);
382
+ }, []);
383
+ const totalCount = costData?.meta?.count || 0;
384
+ const isAllSelected = displayData !== null && selectedRows.size === displayData.projects.length && displayData.projects.length > 0;
385
+ const isIndeterminate = displayData !== null && selectedRows.size > 0 && selectedRows.size < displayData.projects.length;
386
+ const columns = useMemo(() => {
387
+ const getChangeColor = (change) => {
388
+ if (change < 0) return "#d32f2f";
389
+ if (isDarkMode) return "#4BB543";
390
+ return "#2e7d32";
391
+ };
392
+ const cols = [
393
+ {
394
+ title: /* @__PURE__ */ React.createElement("div", { style: { marginLeft: 0, paddingLeft: 0 } }, /* @__PURE__ */ React.createElement(
395
+ "input",
396
+ {
397
+ type: "checkbox",
398
+ checked: isAllSelected,
399
+ ref: (input) => {
400
+ if (input) input.indeterminate = isIndeterminate;
401
+ },
402
+ onChange: (e) => {
403
+ e.stopPropagation();
404
+ handleSelectAll(e.target.checked);
405
+ },
406
+ style: {
407
+ width: "18px",
408
+ height: "18px",
409
+ cursor: "pointer",
410
+ margin: 0,
411
+ padding: 0
412
+ }
413
+ }
414
+ )),
415
+ field: "checkbox",
416
+ sorting: false,
417
+ width: "40px",
418
+ cellStyle: {
419
+ paddingLeft: "4px",
420
+ paddingRight: "4px"
421
+ },
422
+ headerStyle: {
423
+ paddingLeft: "4px",
424
+ paddingRight: "4px"
425
+ },
426
+ render: (data) => /* @__PURE__ */ React.createElement("div", { style: { paddingRight: "4px" } }, /* @__PURE__ */ React.createElement(
427
+ "input",
428
+ {
429
+ type: "checkbox",
430
+ checked: selectedRows.has(data.id),
431
+ onChange: (e) => handleRowSelect(data.id, e.target.checked),
432
+ style: {
433
+ width: "18px",
434
+ height: "18px",
435
+ cursor: "pointer",
436
+ margin: 0,
437
+ padding: 0
438
+ }
439
+ }
440
+ ))
441
+ },
442
+ {
443
+ title: /* @__PURE__ */ React.createElement(Typography, { variant: "body2", style: { fontWeight: "bold" } }, groupBy.charAt(0).toLocaleUpperCase("en-US") + groupBy.slice(1), " ", "name"),
444
+ field: "projectName",
445
+ cellStyle: { minWidth: 350 },
446
+ headerStyle: { minWidth: 350 },
447
+ render: (data) => /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
448
+ "div",
449
+ {
450
+ style: {
451
+ display: "flex",
452
+ alignItems: "center",
453
+ justifyContent: "space-between",
454
+ gap: "8px"
455
+ }
456
+ },
457
+ /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, data.projectName),
458
+ groupBy === "project" && data.includesOverhead && /* @__PURE__ */ React.createElement(
459
+ Typography,
460
+ {
461
+ variant: "caption",
462
+ style: {
463
+ padding: "2px 6px",
464
+ backgroundColor: "#F5F5F5",
465
+ border: "1px solid #D2D2D2",
466
+ borderRadius: "16px",
467
+ color: "black",
468
+ whiteSpace: "nowrap"
469
+ }
470
+ },
471
+ "Includes overhead"
472
+ )
473
+ ), groupBy === "cluster" && data.clusterId && /* @__PURE__ */ React.createElement(
474
+ Typography,
475
+ {
476
+ variant: "caption",
477
+ style: { color: "#666", display: "block", marginTop: 10 }
478
+ },
479
+ data.clusterId
480
+ ))
481
+ }
482
+ ];
483
+ if (showMonthOverMonthChange) {
484
+ cols.push({
485
+ title: "Month over month change",
486
+ field: "monthOverMonthChange",
487
+ sorting: false,
488
+ render: (data) => /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
489
+ "div",
490
+ {
491
+ style: {
492
+ color: getChangeColor(data.monthOverMonthChange)
493
+ }
494
+ },
495
+ Math.abs(data.monthOverMonthChange).toFixed(2),
496
+ "%",
497
+ data.monthOverMonthChange > 0 ? " \u25B2" : " \u25BC"
498
+ ), /* @__PURE__ */ React.createElement("div", { style: { fontSize: "0.75rem", color: "#666" } }, formatCurrency(
499
+ data.monthOverMonthValue,
500
+ displayData?.currencyCode || ""
501
+ ), " ", "for ", getDateRangeText(timeRange)))
502
+ });
503
+ }
504
+ cols.push({
505
+ title: "Cost",
506
+ field: "cost",
507
+ render: (data) => /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, formatCurrency(data.cost, displayData?.currencyCode || "")), /* @__PURE__ */ React.createElement("div", { style: { fontSize: "0.75rem", color: "#666" } }, data.costPercentage.toFixed(2), "% of cost"))
508
+ });
509
+ if (showInfrastructureCost) {
510
+ cols.push({
511
+ title: "Infrastructure cost",
512
+ field: "infrastructureCost",
513
+ render: (data) => /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, formatCurrency(
514
+ data.infrastructureCost,
515
+ displayData?.currencyCode || ""
516
+ )), /* @__PURE__ */ React.createElement("div", { style: { fontSize: "0.75rem", color: "#666" } }, data.infrastructureCostPercentage.toFixed(2), "% of cost"))
517
+ });
518
+ }
519
+ if (showSupplementaryCost) {
520
+ cols.push({
521
+ title: "Supplementary cost",
522
+ field: "supplementaryCost",
523
+ render: (data) => /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", null, formatCurrency(
524
+ data.supplementaryCost,
525
+ displayData?.currencyCode || ""
526
+ )), /* @__PURE__ */ React.createElement("div", { style: { fontSize: "0.75rem", color: "#666" } }, data.supplementaryCostPercentage.toFixed(2), "% of cost"))
527
+ });
528
+ }
529
+ cols.push({
530
+ title: "Actions",
531
+ field: "actions",
532
+ sorting: false,
533
+ render: (data) => /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: "8px" } }, /* @__PURE__ */ React.createElement(
534
+ DownloadIconButton,
535
+ {
536
+ label: "CSV",
537
+ variant: isDarkMode ? "white" : "black",
538
+ onClick: () => handleRowDownload(data.projectName, "csv"),
539
+ disabled: isDownloading
540
+ }
541
+ ), /* @__PURE__ */ React.createElement(
542
+ DownloadIconButton,
543
+ {
544
+ label: "JSON",
545
+ variant: isDarkMode ? "white" : "black",
546
+ onClick: () => handleRowDownload(data.projectName, "json"),
547
+ disabled: isDownloading
548
+ }
549
+ ))
550
+ });
551
+ return cols;
552
+ }, [
553
+ handleRowSelect,
554
+ handleRowDownload,
555
+ isAllSelected,
556
+ isIndeterminate,
557
+ isDownloading,
558
+ selectedRows,
559
+ handleSelectAll,
560
+ displayData?.currencyCode,
561
+ groupBy,
562
+ showMonthOverMonthChange,
563
+ showInfrastructureCost,
564
+ showSupplementaryCost,
565
+ timeRange,
566
+ isDarkMode
567
+ ]);
568
+ const handleOrderChange = useCallback(
569
+ (orderBy, orderDirection) => {
570
+ if (orderBy === -1) {
571
+ return;
572
+ }
573
+ const column = columns[orderBy];
574
+ if (column?.field && column.field !== "actions" && column.sorting !== false) {
575
+ const newField = column.field;
576
+ setSortField(newField);
577
+ setSortDirection(orderDirection);
578
+ setCurrentPage(0);
579
+ }
580
+ },
581
+ [columns]
582
+ );
583
+ if (error) {
584
+ return /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error });
585
+ }
586
+ return /* @__PURE__ */ React.createElement(BasePage, { pageTitle: "", withContentPadding: true }, /* @__PURE__ */ React.createElement(
587
+ PageHeader,
588
+ {
589
+ totalCost: displayData.totalCost,
590
+ month: displayData.month,
591
+ endDate: displayData.endDate,
592
+ currencyCode: displayData.currencyCode,
593
+ customStyle: { marginTop: "-24px" }
594
+ }
595
+ ), /* @__PURE__ */ React.createElement(
596
+ Divider,
597
+ {
598
+ style: {
599
+ marginBottom: 24,
600
+ marginLeft: "-24px",
601
+ marginRight: "-24px",
602
+ width: "calc(100% + 48px)"
603
+ }
604
+ }
605
+ ), /* @__PURE__ */ React.createElement(PageLayout, null, /* @__PURE__ */ React.createElement(PageLayout.Filters, null, /* @__PURE__ */ React.createElement(
606
+ Filters,
607
+ {
608
+ groupBy,
609
+ overheadDistribution,
610
+ timeRange,
611
+ currency,
612
+ filterBy,
613
+ filterOperation,
614
+ filterValue,
615
+ tags,
616
+ onGroupByChange: (value) => {
617
+ setGroupBy(value);
618
+ setCurrentPage(0);
619
+ if (value !== "tag") {
620
+ setSelectedTag("");
621
+ }
622
+ if (value !== "project") {
623
+ setShowPlatformSum(false);
624
+ }
625
+ setFilterBy(value);
626
+ setFilterValue("");
627
+ setSelectedTagKey("");
628
+ setSelectedTagValue("");
629
+ },
630
+ selectedTag,
631
+ onSelectedTagChange: (value) => {
632
+ setSelectedTag(value);
633
+ setCurrentPage(0);
634
+ },
635
+ onOverheadDistributionChange: (value) => {
636
+ setOverheadDistribution(value);
637
+ setCurrentPage(0);
638
+ },
639
+ onTimeRangeChange: (value) => {
640
+ setTimeRange(value);
641
+ setCurrentPage(0);
642
+ },
643
+ onCurrencyChange: (value) => {
644
+ setCurrency(value);
645
+ setCurrentPage(0);
646
+ },
647
+ onFilterByChange: (value) => {
648
+ setFilterBy(value);
649
+ setCurrentPage(0);
650
+ if (value !== "tag") {
651
+ setSelectedTagKey("");
652
+ setSelectedTagValue("");
653
+ }
654
+ },
655
+ onFilterOperationChange: (value) => {
656
+ setFilterOperation(value);
657
+ setCurrentPage(0);
658
+ },
659
+ onFilterValueChange: (value) => {
660
+ setFilterValue(value);
661
+ setCurrentPage(0);
662
+ },
663
+ selectedTagKey,
664
+ selectedTagValue,
665
+ onSelectedTagKeyChange: (value) => {
666
+ setSelectedTagKey(value);
667
+ setSelectedTagValue("");
668
+ setCurrentPage(0);
669
+ },
670
+ onSelectedTagValueChange: (value) => {
671
+ setSelectedTagValue(value);
672
+ setCurrentPage(0);
673
+ }
674
+ }
675
+ )), /* @__PURE__ */ React.createElement(PageLayout.Table, null, /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement(InfoCard, null, /* @__PURE__ */ React.createElement(
676
+ Table,
677
+ {
678
+ data: displayData?.projects || [],
679
+ isLoading: loading,
680
+ components: {
681
+ Toolbar: () => /* @__PURE__ */ React.createElement(
682
+ TableToolbar,
683
+ {
684
+ showPlatformSum,
685
+ setShowPlatformSum,
686
+ projectsCount: displayData?.projects?.length || 0,
687
+ groupBy,
688
+ showMonthOverMonthChange,
689
+ setShowMonthOverMonthChange,
690
+ showInfrastructureCost,
691
+ setShowInfrastructureCost,
692
+ showSupplementaryCost,
693
+ setShowSupplementaryCost,
694
+ onDownloadCsv: () => handleHeaderDownload("csv"),
695
+ onDownloadJson: () => handleHeaderDownload("json"),
696
+ isDownloading
697
+ }
698
+ )
699
+ },
700
+ columns,
701
+ options: {
702
+ paging: true,
703
+ pageSize,
704
+ pageSizeOptions: [5, 10, 25, 50],
705
+ search: false,
706
+ sorting: true,
707
+ padding: "dense",
708
+ thirdSortClick: false
709
+ },
710
+ totalCount,
711
+ page: currentPage,
712
+ onPageChange: handlePageChange,
713
+ onRowsPerPageChange: handleRowsPerPageChange,
714
+ onOrderChange: handleOrderChange,
715
+ style: { outline: "none" },
716
+ localization: {
717
+ pagination: {
718
+ labelRowsPerPage: "rows"
719
+ }
720
+ }
721
+ }
722
+ ))))));
723
+ }
724
+ OpenShiftPage.displayName = "OpenShiftPage";
725
+
726
+ export { OpenShiftPage };
727
+ //# sourceMappingURL=OpenShiftPage.esm.js.map