@red-hat-developer-hub/plugin-cost-management-common 2.0.1

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 (62) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +17 -0
  3. package/dist/DefaultApiClient.cjs.js +8 -0
  4. package/dist/DefaultApiClient.cjs.js.map +1 -0
  5. package/dist/DefaultApiClient.d.ts +83 -0
  6. package/dist/DefaultApiClient.esm.js +2 -0
  7. package/dist/DefaultApiClient.esm.js.map +1 -0
  8. package/dist/clients/cost-management/CostManagementSlimClient.cjs.js +378 -0
  9. package/dist/clients/cost-management/CostManagementSlimClient.cjs.js.map +1 -0
  10. package/dist/clients/cost-management/CostManagementSlimClient.esm.js +372 -0
  11. package/dist/clients/cost-management/CostManagementSlimClient.esm.js.map +1 -0
  12. package/dist/clients/optimizations/OptimizationsClient.cjs.js +99 -0
  13. package/dist/clients/optimizations/OptimizationsClient.cjs.js.map +1 -0
  14. package/dist/clients/optimizations/OptimizationsClient.esm.js +92 -0
  15. package/dist/clients/optimizations/OptimizationsClient.esm.js.map +1 -0
  16. package/dist/clients/orchestrator-slim/OrchestratorSlimClient.cjs.js +107 -0
  17. package/dist/clients/orchestrator-slim/OrchestratorSlimClient.cjs.js.map +1 -0
  18. package/dist/clients/orchestrator-slim/OrchestratorSlimClient.esm.js +105 -0
  19. package/dist/clients/orchestrator-slim/OrchestratorSlimClient.esm.js.map +1 -0
  20. package/dist/clients.cjs.js +12 -0
  21. package/dist/clients.cjs.js.map +1 -0
  22. package/dist/clients.d.ts +432 -0
  23. package/dist/clients.esm.js +4 -0
  24. package/dist/clients.esm.js.map +1 -0
  25. package/dist/generated/apis/DefaultApi.client.cjs.js +91 -0
  26. package/dist/generated/apis/DefaultApi.client.cjs.js.map +1 -0
  27. package/dist/generated/apis/DefaultApi.client.esm.js +66 -0
  28. package/dist/generated/apis/DefaultApi.client.esm.js.map +1 -0
  29. package/dist/generated/pluginId.cjs.js +6 -0
  30. package/dist/generated/pluginId.cjs.js.map +1 -0
  31. package/dist/generated/pluginId.esm.js +4 -0
  32. package/dist/generated/pluginId.esm.js.map +1 -0
  33. package/dist/index.cjs.js +14 -0
  34. package/dist/index.cjs.js.map +1 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.esm.js +5 -0
  37. package/dist/index.esm.js.map +1 -0
  38. package/dist/json-utils.cjs.js +22 -0
  39. package/dist/json-utils.cjs.js.map +1 -0
  40. package/dist/json-utils.d.ts +46 -0
  41. package/dist/json-utils.esm.js +20 -0
  42. package/dist/json-utils.esm.js.map +1 -0
  43. package/dist/models.cjs.js +3 -0
  44. package/dist/models.cjs.js.map +1 -0
  45. package/dist/models.d.ts +25 -0
  46. package/dist/models.esm.js +2 -0
  47. package/dist/models.esm.js.map +1 -0
  48. package/dist/permissions.cjs.js +47 -0
  49. package/dist/permissions.cjs.js.map +1 -0
  50. package/dist/permissions.d.ts +24 -0
  51. package/dist/permissions.esm.js +36 -0
  52. package/dist/permissions.esm.js.map +1 -0
  53. package/dist/types/RecommendationList.model.d-J74WXrJI.d.ts +414 -0
  54. package/dist/util/lib/json.cjs.js +20 -0
  55. package/dist/util/lib/json.cjs.js.map +1 -0
  56. package/dist/util/lib/json.esm.js +15 -0
  57. package/dist/util/lib/json.esm.js.map +1 -0
  58. package/dist/util/lib/json_node.cjs.js +191 -0
  59. package/dist/util/lib/json_node.cjs.js.map +1 -0
  60. package/dist/util/lib/json_node.esm.js +189 -0
  61. package/dist/util/lib/json_node.esm.js.map +1 -0
  62. package/package.json +124 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # @red-hat-developer-hub/plugin-cost-management-common
2
+
3
+ ## 2.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - a889090: - Migrate from rhdh-theme to the theme within rhdh-plugins repo
8
+ - Fix package name in README and CHANGELOG
9
+ - Align pluginId in package.json to group pluginPackages
10
+ - 03c0a11: Fix #1317: updated the outdated repository reference.
11
+
12
+ ## 2.0.0
13
+
14
+ ### Major Changes
15
+
16
+ - 02bf923: plugin for viewing and applying Red Hat Insight recommendations
17
+ - 67c18a8: sync with original repo
18
+
19
+ ## 1.1.0
20
+
21
+ ### Minor Changes
22
+
23
+ - 613061d: bump backstage to 1.34.2 and remove @spotify/prettier-config
24
+
25
+ ## 1.0.3
26
+
27
+ ### Patch Changes
28
+
29
+ - 3a1aab2: Backstage version bump to v1.32.2
30
+
31
+ ## 1.0.2
32
+
33
+ ### Patch Changes
34
+
35
+ - df1288f: Backstage version bump to v1.31.2
36
+
37
+ ## 1.0.1
38
+
39
+ ### Patch Changes
40
+
41
+ - b209d3b: Backstage version bump to v1.30.2
42
+
43
+ ## 1.0.0
44
+
45
+ ### Major Changes
46
+
47
+ - 02dd072: Adds the cost-management plugin
48
+
49
+ This is the first iteration for this plugin. Your feedback is more than welcome!
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # Resource Optimization Common
2
+
3
+ Shared isomorphic code for the cost-management plugin.
4
+
5
+ ## Generating the API client
6
+
7
+ The client is generated from the Optimizations (ROS) OpenAPI spec. The spec's `info.title` is set to `cost-management` (see [Generating API Clients](https://backstage.io/docs/openapi/generate-client/)). Run from the common package:
8
+
9
+ ```sh
10
+ yarn generate-client
11
+ ```
12
+
13
+ If a Cost Management reports API OpenAPI spec becomes available, the script in `scripts/generate_client.mjs` can be extended to fetch or merge it.
14
+
15
+ ## Links
16
+
17
+ - [Resource Optimization front-end plugin](../cost-management/README.md)
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ var DefaultApi_client = require('./generated/apis/DefaultApi.client.cjs.js');
4
+
5
+
6
+
7
+ exports.DefaultApiClient = DefaultApi_client.DefaultApiClient;
8
+ //# sourceMappingURL=DefaultApiClient.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DefaultApiClient.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
@@ -0,0 +1,83 @@
1
+ import { R as RecommendationBoxPlots, U as RecommendationList } from './types/RecommendationList.model.d-J74WXrJI.js';
2
+
3
+ /**
4
+ * Wraps the Response type to convey a type on the json call.
5
+ *
6
+ * @public
7
+ */
8
+ type TypedResponse<T> = Omit<Response, 'json'> & {
9
+ json: () => Promise<T>;
10
+ };
11
+ /**
12
+ * Options you can pass into a request for additional information.
13
+ *
14
+ * @public
15
+ */
16
+ interface RequestOptions {
17
+ token?: string;
18
+ }
19
+ /**
20
+ * no description
21
+ * @public
22
+ */
23
+ declare class DefaultApiClient {
24
+ private readonly discoveryApi;
25
+ private readonly fetchApi;
26
+ constructor(options: {
27
+ discoveryApi: {
28
+ getBaseUrl(pluginId: string): Promise<string>;
29
+ };
30
+ fetchApi?: {
31
+ fetch: typeof fetch;
32
+ };
33
+ });
34
+ /**
35
+ * This feature is in limited preview for select customers
36
+ * Get recommendation for container
37
+ * @param recommendationId - The recommendation UUID
38
+ * @param memoryUnit - unit preference for memory
39
+ * @param cpuUnit - unit preference for cpu
40
+ */
41
+ getRecommendationById(request: {
42
+ path: {
43
+ recommendationId: string;
44
+ };
45
+ query: {
46
+ memoryUnit?: 'bytes' | 'MiB' | 'GiB';
47
+ cpuUnit?: 'millicores' | 'cores';
48
+ };
49
+ }, options?: RequestOptions): Promise<TypedResponse<RecommendationBoxPlots>>;
50
+ /**
51
+ * This feature is in limited preview for select customers
52
+ * Get all recommendations
53
+ * @param cluster - Cluster alias or UUID
54
+ * @param workloadType - Options are daemonset, deployment, deploymentconfig, replicaset, replicationcontroller, statefulset
55
+ * @param workload - Workload name
56
+ * @param container - Container name
57
+ * @param project - Project name
58
+ * @param startDate - Start date
59
+ * @param endDate - End date
60
+ * @param offset - Pagination offset
61
+ * @param limit - Pagination limit
62
+ * @param orderBy - Options are cluster, project, workload_type, workload, container, last_reported
63
+ * @param orderHow - Options are ASC, DESC
64
+ */
65
+ getRecommendationList(request: {
66
+ query: {
67
+ cluster?: Array<string>;
68
+ workloadType?: Array<string>;
69
+ workload?: Array<string>;
70
+ container?: Array<string>;
71
+ project?: Array<string>;
72
+ startDate?: string;
73
+ endDate?: string;
74
+ offset?: number;
75
+ limit?: number;
76
+ orderBy?: 'cluster' | 'project' | 'workload_type' | 'workload' | 'container' | 'last_reported';
77
+ orderHow?: 'asc' | 'desc';
78
+ };
79
+ }, options?: RequestOptions): Promise<TypedResponse<RecommendationList>>;
80
+ }
81
+
82
+ export { DefaultApiClient };
83
+ export type { RequestOptions, TypedResponse };
@@ -0,0 +1,2 @@
1
+ export { DefaultApiClient } from './generated/apis/DefaultApi.client.esm.js';
2
+ //# sourceMappingURL=DefaultApiClient.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DefaultApiClient.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,378 @@
1
+ 'use strict';
2
+
3
+ var crossFetch = require('cross-fetch');
4
+ var pluginId = require('../../generated/pluginId.cjs.js');
5
+
6
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
7
+
8
+ var crossFetch__default = /*#__PURE__*/_interopDefaultCompat(crossFetch);
9
+
10
+ class CostManagementSlimClient {
11
+ discoveryApi;
12
+ fetchApi;
13
+ constructor(options) {
14
+ this.discoveryApi = options.discoveryApi;
15
+ this.fetchApi = options.fetchApi ?? { fetch: crossFetch__default.default };
16
+ }
17
+ async getCostManagementReport(request) {
18
+ const baseUrl = await this.discoveryApi.getBaseUrl(pluginId.pluginId);
19
+ const uri = "/proxy/reports/openshift/costs/";
20
+ const queryParams = this.buildCostManagementQueryParams(request);
21
+ const queryString = queryParams.toString();
22
+ const queryPart = queryString ? `?${queryString}` : "";
23
+ const url = `${baseUrl}${uri}${queryPart}`;
24
+ return await this.fetchViaBackendProxy(url);
25
+ }
26
+ async downloadCostManagementReport(request) {
27
+ const baseUrl = await this.discoveryApi.getBaseUrl(pluginId.pluginId);
28
+ const uri = "/proxy/reports/openshift/costs/";
29
+ const downloadRequest = {
30
+ query: {
31
+ ...request.query,
32
+ "filter[limit]": 0
33
+ }
34
+ };
35
+ delete downloadRequest.query["filter[offset]"];
36
+ const queryParams = this.buildCostManagementQueryParams(downloadRequest);
37
+ const queryString = queryParams.toString();
38
+ const queryPart = queryString ? `?${queryString}` : "";
39
+ const url = `${baseUrl}${uri}${queryPart}`;
40
+ const acceptHeader = request.format === "csv" ? "text/csv" : "application/json";
41
+ const response = await this.fetchApi.fetch(url, {
42
+ headers: {
43
+ Accept: acceptHeader
44
+ },
45
+ method: "GET"
46
+ });
47
+ if (!response.ok) {
48
+ throw new Error(response.statusText);
49
+ }
50
+ const responseContentType = response.headers.get("content-type") || "";
51
+ const responseText = await response.text();
52
+ let data;
53
+ let mimeType;
54
+ if (request.format === "csv") {
55
+ data = responseText;
56
+ mimeType = "text/csv";
57
+ } else {
58
+ if (responseContentType.includes("application/json")) {
59
+ const jsonData = JSON.parse(responseText);
60
+ data = JSON.stringify(jsonData, null, 2);
61
+ } else {
62
+ data = this.convertCsvToJson(responseText);
63
+ }
64
+ mimeType = "application/json";
65
+ }
66
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-");
67
+ const extension = request.format;
68
+ const filename = `openshift-costs-${timestamp}.${extension}`;
69
+ const blob = new Blob([data], { type: mimeType });
70
+ const downloadUrl = URL.createObjectURL(blob);
71
+ const link = document.createElement("a");
72
+ link.href = downloadUrl;
73
+ link.download = filename;
74
+ document.body.appendChild(link);
75
+ link.click();
76
+ link.remove();
77
+ URL.revokeObjectURL(downloadUrl);
78
+ }
79
+ /**
80
+ * Converts CSV string to JSON string
81
+ * @param csv - The CSV data as a string
82
+ * @returns JSON string representation of the CSV data
83
+ */
84
+ convertCsvToJson(csv) {
85
+ const normalizedCsv = csv.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
86
+ const lines = normalizedCsv.trim().split("\n");
87
+ if (lines.length === 0) {
88
+ return JSON.stringify({ data: [] }, null, 2);
89
+ }
90
+ const headers = this.parseCsvLine(lines[0]);
91
+ const data = [];
92
+ for (let i = 1; i < lines.length; i++) {
93
+ const values = this.parseCsvLine(lines[i]);
94
+ const row = {};
95
+ for (let j = 0; j < headers.length; j++) {
96
+ const header = headers[j];
97
+ const value = values[j] || "";
98
+ if (value === "") {
99
+ row[header] = null;
100
+ } else if (!Number.isNaN(Number(value)) && value !== "") {
101
+ row[header] = Number(value);
102
+ } else {
103
+ row[header] = value;
104
+ }
105
+ }
106
+ data.push(row);
107
+ }
108
+ return JSON.stringify({ data }, null, 2);
109
+ }
110
+ /**
111
+ * Parses a CSV line handling quoted values
112
+ * @param line - A single line from CSV
113
+ * @returns Array of values
114
+ */
115
+ parseCsvLine(line) {
116
+ const result = [];
117
+ let current = "";
118
+ let inQuotes = false;
119
+ let skipNext = false;
120
+ for (let i = 0; i < line.length; i++) {
121
+ if (skipNext) {
122
+ skipNext = false;
123
+ continue;
124
+ }
125
+ const char = line[i];
126
+ if (char === '"') {
127
+ if (inQuotes && line[i + 1] === '"') {
128
+ current += '"';
129
+ skipNext = true;
130
+ } else {
131
+ inQuotes = !inQuotes;
132
+ }
133
+ } else if (char === "," && !inQuotes) {
134
+ result.push(current);
135
+ current = "";
136
+ } else {
137
+ current += char;
138
+ }
139
+ }
140
+ result.push(current);
141
+ return result;
142
+ }
143
+ /**
144
+ * Builds query parameters for cost management report request
145
+ * @param request - The cost management request
146
+ * @returns URLSearchParams with all query parameters
147
+ */
148
+ buildCostManagementQueryParams(request) {
149
+ const queryParams = new URLSearchParams();
150
+ this.appendBasicQueryParams(queryParams, request);
151
+ this.appendDynamicParams(queryParams, request, "group_by[");
152
+ this.appendDynamicParams(queryParams, request, "order_by[");
153
+ this.appendDynamicFilterParams(queryParams, request);
154
+ this.appendDynamicExcludeParams(queryParams, request);
155
+ return queryParams;
156
+ }
157
+ /**
158
+ * Appends basic query parameters (currency, delta, and standard filters)
159
+ */
160
+ appendBasicQueryParams(queryParams, request) {
161
+ const basicParams = [
162
+ { key: "currency", paramName: "currency" },
163
+ { key: "delta", paramName: "delta" },
164
+ { key: "category", paramName: "category" },
165
+ {
166
+ key: "filter[limit]",
167
+ paramName: "filter[limit]",
168
+ needsStringConversion: true
169
+ },
170
+ {
171
+ key: "filter[offset]",
172
+ paramName: "filter[offset]",
173
+ needsStringConversion: true
174
+ },
175
+ { key: "filter[resolution]", paramName: "filter[resolution]" },
176
+ {
177
+ key: "filter[time_scope_units]",
178
+ paramName: "filter[time_scope_units]"
179
+ },
180
+ {
181
+ key: "filter[time_scope_value]",
182
+ paramName: "filter[time_scope_value]",
183
+ needsStringConversion: true
184
+ },
185
+ {
186
+ key: "filter[exact:cluster]",
187
+ paramName: "filter[exact:cluster]"
188
+ }
189
+ ];
190
+ for (const { key, paramName, needsStringConversion } of basicParams) {
191
+ const value = request.query[key];
192
+ if (value !== void 0 && value !== null && value !== "") {
193
+ queryParams.append(
194
+ paramName,
195
+ needsStringConversion ? String(value) : value
196
+ );
197
+ }
198
+ }
199
+ }
200
+ /**
201
+ * Appends dynamic parameters that match a specific prefix (e.g., 'group_by[', 'order_by[')
202
+ */
203
+ appendDynamicParams(queryParams, request, prefix) {
204
+ for (const key of Object.keys(request.query)) {
205
+ if (key.startsWith(prefix) && key.endsWith("]")) {
206
+ const value = request.query[key];
207
+ if (value) {
208
+ queryParams.append(key, String(value));
209
+ }
210
+ }
211
+ }
212
+ }
213
+ /**
214
+ * Appends dynamic filter parameters, excluding those already handled explicitly
215
+ */
216
+ appendDynamicFilterParams(queryParams, request) {
217
+ const handledFilterKeys = /* @__PURE__ */ new Set([
218
+ "filter[limit]",
219
+ "filter[offset]",
220
+ "filter[resolution]",
221
+ "filter[time_scope_units]",
222
+ "filter[time_scope_value]"
223
+ ]);
224
+ for (const key of Object.keys(request.query)) {
225
+ if (key.startsWith("filter[") && key.endsWith("]") && !handledFilterKeys.has(key)) {
226
+ const value = request.query[key];
227
+ if (value) {
228
+ queryParams.append(key, String(value));
229
+ }
230
+ }
231
+ }
232
+ }
233
+ /**
234
+ * Appends dynamic exclude parameters (e.g., exclude[project], exclude[cluster], exclude[tag:key])
235
+ */
236
+ appendDynamicExcludeParams(queryParams, request) {
237
+ for (const key of Object.keys(request.query)) {
238
+ if (key.startsWith("exclude[") && key.endsWith("]")) {
239
+ const value = request.query[key];
240
+ if (value) {
241
+ queryParams.append(key, String(value));
242
+ }
243
+ }
244
+ }
245
+ }
246
+ /**
247
+ * Search OpenShift projects
248
+ * @param search - Search term to filter projects
249
+ * @param options - Optional request options including token and limit
250
+ * @param options.token - Bearer token for authentication (backend use)
251
+ * @param options.limit - Maximum number of results to return (default: 10, use large number for all)
252
+ */
253
+ async searchOpenShiftProjects(search = "", options) {
254
+ const url = await this.buildResourceTypeUrl(
255
+ "openshift-projects",
256
+ search,
257
+ options?.limit
258
+ );
259
+ if (options?.token) {
260
+ return await this.fetchWithExternalToken(url, options.token);
261
+ }
262
+ return await this.fetchResourceType(url);
263
+ }
264
+ /**
265
+ * Search OpenShift clusters
266
+ * @param search - Search term to filter clusters
267
+ */
268
+ async searchOpenShiftClusters(search = "", options) {
269
+ const url = await this.buildResourceTypeUrl(
270
+ "openshift-clusters",
271
+ search,
272
+ options?.limit
273
+ );
274
+ if (options?.token) {
275
+ return await this.fetchWithExternalToken(url, options.token);
276
+ }
277
+ return await this.fetchResourceType(url);
278
+ }
279
+ /**
280
+ * Search OpenShift nodes
281
+ * @param search - Search term to filter nodes
282
+ */
283
+ async searchOpenShiftNodes(search = "") {
284
+ const baseUrl = await this.discoveryApi.getBaseUrl(pluginId.pluginId);
285
+ const searchParam = search ? `?search=${encodeURIComponent(search)}` : "";
286
+ const url = `${baseUrl}/proxy/resource-types/openshift-nodes/${searchParam}`;
287
+ return await this.fetchResourceType(url);
288
+ }
289
+ /**
290
+ * Get OpenShift tags
291
+ * @param timeScopeValue - Time scope value (-1 for month-to-date, -2 for previous-month). Defaults to -1.
292
+ * @returns TypedResponse with array of tag strings
293
+ */
294
+ async getOpenShiftTags(timeScopeValue = -1) {
295
+ const baseUrl = await this.discoveryApi.getBaseUrl(pluginId.pluginId);
296
+ const url = `${baseUrl}/proxy/tags/openshift/?filter[time_scope_value]=${timeScopeValue}&key_only=true&limit=1000`;
297
+ return await this.fetchViaBackendProxy(url);
298
+ }
299
+ /**
300
+ * Get OpenShift tag values for a specific tag key
301
+ * @param tagKey - The tag key to get values for
302
+ * @param timeScopeValue - Time scope value (-1 for month-to-date, -2 for previous-month). Defaults to -1.
303
+ * @returns TypedResponse with array of tag value objects
304
+ */
305
+ async getOpenShiftTagValues(tagKey, timeScopeValue = -1) {
306
+ const baseUrl = await this.discoveryApi.getBaseUrl(pluginId.pluginId);
307
+ const url = `${baseUrl}/proxy/tags/openshift/?filter[key]=${encodeURIComponent(
308
+ tagKey
309
+ )}&filter[time_scope_value]=${timeScopeValue}`;
310
+ return await this.fetchViaBackendProxy(url);
311
+ }
312
+ async fetchResourceType(url) {
313
+ return await this.fetchViaBackendProxy(url);
314
+ }
315
+ /**
316
+ * Builds resource type URL routed through the backend secure proxy
317
+ */
318
+ async buildResourceTypeUrl(resourceType, search, limit) {
319
+ const baseUrl = await this.discoveryApi.getBaseUrl(pluginId.pluginId);
320
+ const params = new URLSearchParams();
321
+ if (search) {
322
+ params.append("search", search);
323
+ }
324
+ if (limit !== void 0) {
325
+ params.append("limit", String(limit));
326
+ }
327
+ const queryString = params.toString();
328
+ const queryPart = queryString ? `?${queryString}` : "";
329
+ return `${baseUrl}/proxy/resource-types/${resourceType}/${queryPart}`;
330
+ }
331
+ /**
332
+ * Fetches with externally provided token (for backend use).
333
+ * This path bypasses the secure proxy since it's already server-side.
334
+ */
335
+ async fetchWithExternalToken(url, token) {
336
+ const response = await this.fetchApi.fetch(url, {
337
+ headers: {
338
+ "Content-Type": "application/json",
339
+ Authorization: `Bearer ${token}`
340
+ },
341
+ method: "GET"
342
+ });
343
+ if (!response.ok) {
344
+ throw new Error(response.statusText);
345
+ }
346
+ return {
347
+ ok: response.ok,
348
+ status: response.status,
349
+ json: async () => response.json()
350
+ };
351
+ }
352
+ /**
353
+ * Fetches a URL via the backend secure proxy. No Authorization header
354
+ * is sent; the backend injects the SSO token and RBAC filters
355
+ * server-side. Authentication is via the Backstage session cookie.
356
+ */
357
+ async fetchViaBackendProxy(url) {
358
+ const response = await this.fetchApi.fetch(url, {
359
+ headers: {
360
+ "Content-Type": "application/json"
361
+ },
362
+ method: "GET"
363
+ });
364
+ if (!response.ok) {
365
+ throw new Error(response.statusText);
366
+ }
367
+ return {
368
+ ...response,
369
+ json: async () => {
370
+ const data = await response.json();
371
+ return data;
372
+ }
373
+ };
374
+ }
375
+ }
376
+
377
+ exports.CostManagementSlimClient = CostManagementSlimClient;
378
+ //# sourceMappingURL=CostManagementSlimClient.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CostManagementSlimClient.cjs.js","sources":["../../../src/clients/cost-management/CostManagementSlimClient.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 type { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\nimport crossFetch from 'cross-fetch';\nimport { pluginId } from '../../generated/pluginId';\nimport type { TypedResponse } from '../../generated/apis';\nimport type {\n CostManagementReport,\n GetCostManagementRequest,\n DownloadCostManagementRequest,\n} from './types';\nimport type { CostManagementSlimApi } from './CostManagementSlimApi';\n/**\n * Client for Cost Management data.\n *\n * In the frontend, requests are routed through the backend plugin's secure\n * proxy (`/api/cost-management/proxy/...`), which keeps the SSO token\n * server-side and enforces RBAC before forwarding to the Cost Management API.\n *\n * @public\n */\nexport class CostManagementSlimClient implements CostManagementSlimApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n\n constructor(options: { discoveryApi: DiscoveryApi; fetchApi?: FetchApi }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi ?? { fetch: crossFetch };\n }\n\n public async getCostManagementReport(\n request: GetCostManagementRequest,\n ): Promise<TypedResponse<CostManagementReport>> {\n const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);\n const uri = '/proxy/reports/openshift/costs/';\n\n const queryParams = this.buildCostManagementQueryParams(request);\n\n const queryString = queryParams.toString();\n const queryPart = queryString ? `?${queryString}` : '';\n const url = `${baseUrl}${uri}${queryPart}`;\n\n return await this.fetchViaBackendProxy<CostManagementReport>(url);\n }\n\n public async downloadCostManagementReport(\n request: DownloadCostManagementRequest,\n ): Promise<void> {\n const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);\n const uri = '/proxy/reports/openshift/costs/';\n\n const downloadRequest: GetCostManagementRequest = {\n query: {\n ...request.query,\n 'filter[limit]': 0,\n },\n };\n delete downloadRequest.query['filter[offset]'];\n\n const queryParams = this.buildCostManagementQueryParams(downloadRequest);\n\n const queryString = queryParams.toString();\n const queryPart = queryString ? `?${queryString}` : '';\n const url = `${baseUrl}${uri}${queryPart}`;\n\n const acceptHeader =\n request.format === 'csv' ? 'text/csv' : 'application/json';\n\n const response = await this.fetchApi.fetch(url, {\n headers: {\n Accept: acceptHeader,\n },\n method: 'GET',\n });\n\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n\n // Get the response data\n const responseContentType = response.headers.get('content-type') || '';\n const responseText = await response.text();\n\n let data: string;\n let mimeType: string;\n\n if (request.format === 'csv') {\n // User wants CSV - use response as-is (API returns CSV by default)\n data = responseText;\n mimeType = 'text/csv';\n } else {\n // User wants JSON\n if (responseContentType.includes('application/json')) {\n // Response is already JSON\n const jsonData = JSON.parse(responseText);\n data = JSON.stringify(jsonData, null, 2);\n } else {\n // Response is CSV, need to convert to JSON\n data = this.convertCsvToJson(responseText);\n }\n mimeType = 'application/json';\n }\n\n // Generate filename with timestamp\n const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-');\n const extension = request.format;\n const filename = `openshift-costs-${timestamp}.${extension}`;\n\n // Trigger download in browser\n const blob = new Blob([data], { type: mimeType });\n const downloadUrl = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = downloadUrl;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n link.remove();\n URL.revokeObjectURL(downloadUrl);\n }\n\n /**\n * Converts CSV string to JSON string\n * @param csv - The CSV data as a string\n * @returns JSON string representation of the CSV data\n */\n private convertCsvToJson(csv: string): string {\n // Normalize line endings (handle both \\r\\n and \\r)\n const normalizedCsv = csv.replaceAll('\\r\\n', '\\n').replaceAll('\\r', '\\n');\n const lines = normalizedCsv.trim().split('\\n');\n if (lines.length === 0) {\n return JSON.stringify({ data: [] }, null, 2);\n }\n\n const headers = this.parseCsvLine(lines[0]);\n const data: Record<string, string | number | null>[] = [];\n\n for (let i = 1; i < lines.length; i++) {\n const values = this.parseCsvLine(lines[i]);\n const row: Record<string, string | number | null> = {};\n\n for (let j = 0; j < headers.length; j++) {\n const header = headers[j];\n const value = values[j] || '';\n\n // Try to parse as number if it looks like one\n if (value === '') {\n row[header] = null;\n } else if (!Number.isNaN(Number(value)) && value !== '') {\n row[header] = Number(value);\n } else {\n row[header] = value;\n }\n }\n\n data.push(row);\n }\n\n return JSON.stringify({ data }, null, 2);\n }\n\n /**\n * Parses a CSV line handling quoted values\n * @param line - A single line from CSV\n * @returns Array of values\n */\n private parseCsvLine(line: string): string[] {\n const result: string[] = [];\n let current = '';\n let inQuotes = false;\n let skipNext = false;\n\n for (let i = 0; i < line.length; i++) {\n if (skipNext) {\n skipNext = false;\n continue;\n }\n\n const char = line[i];\n\n if (char === '\"') {\n if (inQuotes && line[i + 1] === '\"') {\n // Escaped quote\n current += '\"';\n skipNext = true;\n } else {\n // Toggle quote mode\n inQuotes = !inQuotes;\n }\n } else if (char === ',' && !inQuotes) {\n result.push(current);\n current = '';\n } else {\n current += char;\n }\n }\n\n result.push(current);\n return result;\n }\n\n /**\n * Builds query parameters for cost management report request\n * @param request - The cost management request\n * @returns URLSearchParams with all query parameters\n */\n private buildCostManagementQueryParams(\n request: GetCostManagementRequest,\n ): URLSearchParams {\n const queryParams = new URLSearchParams();\n this.appendBasicQueryParams(queryParams, request);\n this.appendDynamicParams(queryParams, request, 'group_by[');\n this.appendDynamicParams(queryParams, request, 'order_by[');\n this.appendDynamicFilterParams(queryParams, request);\n this.appendDynamicExcludeParams(queryParams, request);\n return queryParams;\n }\n\n /**\n * Appends basic query parameters (currency, delta, and standard filters)\n */\n private appendBasicQueryParams(\n queryParams: URLSearchParams,\n request: GetCostManagementRequest,\n ): void {\n const basicParams: Array<{\n key: keyof typeof request.query;\n paramName: string;\n needsStringConversion?: boolean;\n }> = [\n { key: 'currency', paramName: 'currency' },\n { key: 'delta', paramName: 'delta' },\n { key: 'category', paramName: 'category' },\n {\n key: 'filter[limit]',\n paramName: 'filter[limit]',\n needsStringConversion: true,\n },\n {\n key: 'filter[offset]',\n paramName: 'filter[offset]',\n needsStringConversion: true,\n },\n { key: 'filter[resolution]', paramName: 'filter[resolution]' },\n {\n key: 'filter[time_scope_units]',\n paramName: 'filter[time_scope_units]',\n },\n {\n key: 'filter[time_scope_value]',\n paramName: 'filter[time_scope_value]',\n needsStringConversion: true,\n },\n {\n key: 'filter[exact:cluster]',\n paramName: 'filter[exact:cluster]',\n },\n ];\n\n for (const { key, paramName, needsStringConversion } of basicParams) {\n const value = request.query[key];\n if (value !== undefined && value !== null && value !== '') {\n queryParams.append(\n paramName,\n needsStringConversion ? String(value) : (value as string),\n );\n }\n }\n }\n\n /**\n * Appends dynamic parameters that match a specific prefix (e.g., 'group_by[', 'order_by[')\n */\n private appendDynamicParams(\n queryParams: URLSearchParams,\n request: GetCostManagementRequest,\n prefix: string,\n ): void {\n for (const key of Object.keys(request.query)) {\n if (key.startsWith(prefix) && key.endsWith(']')) {\n const value = request.query[key as keyof typeof request.query];\n if (value) {\n queryParams.append(key, String(value));\n }\n }\n }\n }\n\n /**\n * Appends dynamic filter parameters, excluding those already handled explicitly\n */\n private appendDynamicFilterParams(\n queryParams: URLSearchParams,\n request: GetCostManagementRequest,\n ): void {\n const handledFilterKeys = new Set([\n 'filter[limit]',\n 'filter[offset]',\n 'filter[resolution]',\n 'filter[time_scope_units]',\n 'filter[time_scope_value]',\n ]);\n\n for (const key of Object.keys(request.query)) {\n if (\n key.startsWith('filter[') &&\n key.endsWith(']') &&\n !handledFilterKeys.has(key)\n ) {\n const value = request.query[key as keyof typeof request.query];\n if (value) {\n queryParams.append(key, String(value));\n }\n }\n }\n }\n\n /**\n * Appends dynamic exclude parameters (e.g., exclude[project], exclude[cluster], exclude[tag:key])\n */\n private appendDynamicExcludeParams(\n queryParams: URLSearchParams,\n request: GetCostManagementRequest,\n ): void {\n for (const key of Object.keys(request.query)) {\n if (key.startsWith('exclude[') && key.endsWith(']')) {\n const value = request.query[key as keyof typeof request.query];\n if (value) {\n queryParams.append(key, String(value));\n }\n }\n }\n }\n\n /**\n * Search OpenShift projects\n * @param search - Search term to filter projects\n * @param options - Optional request options including token and limit\n * @param options.token - Bearer token for authentication (backend use)\n * @param options.limit - Maximum number of results to return (default: 10, use large number for all)\n */\n public async searchOpenShiftProjects(\n search: string = '',\n options?: { token?: string; limit?: number },\n ): Promise<\n TypedResponse<{ data: Array<{ value: string }>; meta?: any; links?: any }>\n > {\n const url = await this.buildResourceTypeUrl(\n 'openshift-projects',\n search,\n options?.limit,\n );\n\n // If token provided externally (backend use), use it directly\n if (options?.token) {\n return await this.fetchWithExternalToken(url, options.token);\n }\n\n // If no external token, use internal token retrieval\n return await this.fetchResourceType(url);\n }\n\n /**\n * Search OpenShift clusters\n * @param search - Search term to filter clusters\n */\n public async searchOpenShiftClusters(\n search: string = '',\n options?: { token?: string; limit?: number },\n ): Promise<\n TypedResponse<{\n data: Array<{ value: string; cluster_alias: string }>;\n meta?: any;\n links?: any;\n }>\n > {\n const url = await this.buildResourceTypeUrl(\n 'openshift-clusters',\n search,\n options?.limit,\n );\n\n // If token provided externally (backend use), use it directly\n if (options?.token) {\n return await this.fetchWithExternalToken(url, options.token);\n }\n\n // If no external token, use internal token retrieval\n return await this.fetchResourceType(url);\n }\n\n /**\n * Search OpenShift nodes\n * @param search - Search term to filter nodes\n */\n public async searchOpenShiftNodes(\n search: string = '',\n ): Promise<\n TypedResponse<{ data: Array<{ value: string }>; meta?: any; links?: any }>\n > {\n const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);\n const searchParam = search ? `?search=${encodeURIComponent(search)}` : '';\n const url = `${baseUrl}/proxy/resource-types/openshift-nodes/${searchParam}`;\n\n return await this.fetchResourceType(url);\n }\n\n /**\n * Get OpenShift tags\n * @param timeScopeValue - Time scope value (-1 for month-to-date, -2 for previous-month). Defaults to -1.\n * @returns TypedResponse with array of tag strings\n */\n public async getOpenShiftTags(\n timeScopeValue: number = -1,\n ): Promise<TypedResponse<{ data: string[]; meta?: any; links?: any }>> {\n const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);\n const url = `${baseUrl}/proxy/tags/openshift/?filter[time_scope_value]=${timeScopeValue}&key_only=true&limit=1000`;\n\n return await this.fetchViaBackendProxy<{\n data: string[];\n meta?: any;\n links?: any;\n }>(url);\n }\n\n /**\n * Get OpenShift tag values for a specific tag key\n * @param tagKey - The tag key to get values for\n * @param timeScopeValue - Time scope value (-1 for month-to-date, -2 for previous-month). Defaults to -1.\n * @returns TypedResponse with array of tag value objects\n */\n public async getOpenShiftTagValues(\n tagKey: string,\n timeScopeValue: number = -1,\n ): Promise<\n TypedResponse<{\n data: Array<{ key: string; values: string[]; enabled: boolean }>;\n meta?: any;\n links?: any;\n }>\n > {\n const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);\n const url = `${baseUrl}/proxy/tags/openshift/?filter[key]=${encodeURIComponent(\n tagKey,\n )}&filter[time_scope_value]=${timeScopeValue}`;\n\n return await this.fetchViaBackendProxy<{\n data: Array<{ key: string; values: string[]; enabled: boolean }>;\n meta?: any;\n links?: any;\n }>(url);\n }\n\n private async fetchResourceType(url: string): Promise<\n TypedResponse<{\n data: Array<{ value: string; cluster_alias: string }>;\n meta?: any;\n links?: any;\n }>\n > {\n return await this.fetchViaBackendProxy<{\n data: Array<{ value: string; cluster_alias: string }>;\n meta?: any;\n links?: any;\n }>(url);\n }\n\n /**\n * Builds resource type URL routed through the backend secure proxy\n */\n private async buildResourceTypeUrl(\n resourceType: string,\n search?: string,\n limit?: number,\n ): Promise<string> {\n const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);\n\n const params = new URLSearchParams();\n if (search) {\n params.append('search', search);\n }\n if (limit !== undefined) {\n params.append('limit', String(limit));\n }\n const queryString = params.toString();\n const queryPart = queryString ? `?${queryString}` : '';\n\n return `${baseUrl}/proxy/resource-types/${resourceType}/${queryPart}`;\n }\n\n /**\n * Fetches with externally provided token (for backend use).\n * This path bypasses the secure proxy since it's already server-side.\n */\n private async fetchWithExternalToken<T>(\n url: string,\n token: string,\n ): Promise<TypedResponse<T>> {\n const response = await this.fetchApi.fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n method: 'GET',\n });\n\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n\n return {\n ok: response.ok,\n status: response.status,\n json: async () => response.json(),\n } as TypedResponse<T>;\n }\n\n /**\n * Fetches a URL via the backend secure proxy. No Authorization header\n * is sent; the backend injects the SSO token and RBAC filters\n * server-side. Authentication is via the Backstage session cookie.\n */\n private async fetchViaBackendProxy<T>(\n url: string,\n ): Promise<TypedResponse<T>> {\n const response = await this.fetchApi.fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'GET',\n });\n\n if (!response.ok) {\n throw new Error(response.statusText);\n }\n\n return {\n ...response,\n json: async () => {\n const data = (await response.json()) as T;\n return data;\n },\n };\n }\n}\n"],"names":["crossFetch","pluginId"],"mappings":"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"}