@electrolux-oss/plugin-infrawallet-backend 1.1.0-20250901061058-90165b1 → 1.1.0-20250929154750-e4ed5e3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cost-clients/AwsClient.cjs.js +34 -7
- package/dist/cost-clients/AwsClient.cjs.js.map +1 -1
- package/dist/cost-clients/AzureClient.cjs.js +31 -2
- package/dist/cost-clients/AzureClient.cjs.js.map +1 -1
- package/dist/cost-clients/ConfluentClient.cjs.js +50 -2
- package/dist/cost-clients/ConfluentClient.cjs.js.map +1 -1
- package/dist/cost-clients/CustomProviderClient.cjs.js +30 -1
- package/dist/cost-clients/CustomProviderClient.cjs.js.map +1 -1
- package/dist/cost-clients/DatadogClient.cjs.js +38 -2
- package/dist/cost-clients/DatadogClient.cjs.js.map +1 -1
- package/dist/cost-clients/ElasticCloudClient.cjs.js +39 -7
- package/dist/cost-clients/ElasticCloudClient.cjs.js.map +1 -1
- package/dist/cost-clients/GCPClient.cjs.js +29 -2
- package/dist/cost-clients/GCPClient.cjs.js.map +1 -1
- package/dist/cost-clients/GitHubClient.cjs.js +28 -2
- package/dist/cost-clients/GitHubClient.cjs.js.map +1 -1
- package/dist/cost-clients/InfraWalletClient.cjs.js +3 -0
- package/dist/cost-clients/InfraWalletClient.cjs.js.map +1 -1
- package/dist/cost-clients/MockClient.cjs.js +18 -0
- package/dist/cost-clients/MockClient.cjs.js.map +1 -1
- package/dist/cost-clients/MongoAtlasClient.cjs.js +56 -24
- package/dist/cost-clients/MongoAtlasClient.cjs.js.map +1 -1
- package/dist/schemas/AzureBilling.cjs.js +15 -7
- package/dist/schemas/AzureBilling.cjs.js.map +1 -1
- package/dist/schemas/ConfluentBilling.cjs.js +20 -8
- package/dist/schemas/ConfluentBilling.cjs.js.map +1 -1
- package/dist/schemas/DatadogBilling.cjs.js +14 -5
- package/dist/schemas/DatadogBilling.cjs.js.map +1 -1
- package/dist/schemas/ElasticBilling.cjs.js +5 -5
- package/dist/schemas/ElasticBilling.cjs.js.map +1 -1
- package/dist/schemas/GCPBilling.cjs.js +48 -18
- package/dist/schemas/GCPBilling.cjs.js.map +1 -1
- package/dist/service/functions.cjs.js +6 -0
- package/dist/service/functions.cjs.js.map +1 -1
- package/dist/tasks/fetchAndSaveCosts.cjs.js +1 -1
- package/dist/tasks/fetchAndSaveCosts.cjs.js.map +1 -1
- package/mock/mock_response.json +252 -0
- package/package.json +1 -1
|
@@ -42,9 +42,25 @@ class CustomProviderClient extends InfraWalletClient.InfraWalletClient {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
let processedRecords = 0;
|
|
46
|
+
let filteredOutZeroAmount = 0;
|
|
47
|
+
let filteredOutMissingFields = 0;
|
|
48
|
+
const filteredOutInvalidDate = 0;
|
|
49
|
+
const filteredOutTimeRange = 0;
|
|
50
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
51
|
+
const totalRecords = Array.isArray(costResponse) ? costResponse.length : 0;
|
|
45
52
|
const transformedData = lodash.reduce(
|
|
46
53
|
costResponse,
|
|
47
54
|
(accumulator, record) => {
|
|
55
|
+
if (!record.provider || !record.account || !record.service || !record.category || record.cost === void 0 || record.cost === null || !record.usage_month) {
|
|
56
|
+
filteredOutMissingFields++;
|
|
57
|
+
return accumulator;
|
|
58
|
+
}
|
|
59
|
+
const cost = parseFloat(record.cost);
|
|
60
|
+
if (cost === 0) {
|
|
61
|
+
filteredOutZeroAmount++;
|
|
62
|
+
return accumulator;
|
|
63
|
+
}
|
|
48
64
|
let periodFormat = "YYYY-MM";
|
|
49
65
|
if (query.granularity === consts.GRANULARITY.DAILY) {
|
|
50
66
|
periodFormat = "YYYY-MM-DD";
|
|
@@ -59,6 +75,7 @@ class CustomProviderClient extends InfraWalletClient.InfraWalletClient {
|
|
|
59
75
|
}
|
|
60
76
|
}
|
|
61
77
|
if (!accumulator[keyName]) {
|
|
78
|
+
uniqueKeys.add(keyName);
|
|
62
79
|
accumulator[keyName] = {
|
|
63
80
|
id: keyName,
|
|
64
81
|
account: record.account,
|
|
@@ -70,10 +87,10 @@ class CustomProviderClient extends InfraWalletClient.InfraWalletClient {
|
|
|
70
87
|
};
|
|
71
88
|
}
|
|
72
89
|
accumulator[keyName] = { ...accumulator[keyName], ...record.tags };
|
|
73
|
-
const cost = parseFloat(record.cost);
|
|
74
90
|
if (query.granularity === consts.GRANULARITY.MONTHLY) {
|
|
75
91
|
const period = moment__default.default(record.usage_month.toString(), "YYYYMM").format(periodFormat);
|
|
76
92
|
accumulator[keyName].reports[period] = cost;
|
|
93
|
+
processedRecords++;
|
|
77
94
|
} else {
|
|
78
95
|
if (record.amortization_mode === "average") {
|
|
79
96
|
const periods = functions.getDailyPeriodStringsForOneMonth(record.usage_month);
|
|
@@ -81,18 +98,30 @@ class CustomProviderClient extends InfraWalletClient.InfraWalletClient {
|
|
|
81
98
|
periods.forEach((period) => {
|
|
82
99
|
accumulator[keyName].reports[period] = averageCost;
|
|
83
100
|
});
|
|
101
|
+
processedRecords += periods.length;
|
|
84
102
|
} else if (record.amortization_mode === "start_day") {
|
|
85
103
|
const period = moment__default.default(record.usage_month.toString(), "YYYYMM").startOf("month").format(periodFormat);
|
|
86
104
|
accumulator[keyName].reports[period] = cost;
|
|
105
|
+
processedRecords++;
|
|
87
106
|
} else {
|
|
88
107
|
const period = moment__default.default(record.usage_month.toString(), "YYYYMM").endOf("month").format(periodFormat);
|
|
89
108
|
accumulator[keyName].reports[period] = cost;
|
|
109
|
+
processedRecords++;
|
|
90
110
|
}
|
|
91
111
|
}
|
|
92
112
|
return accumulator;
|
|
93
113
|
},
|
|
94
114
|
{}
|
|
95
115
|
);
|
|
116
|
+
this.logTransformationSummary({
|
|
117
|
+
processed: processedRecords,
|
|
118
|
+
uniqueReports: uniqueKeys.size,
|
|
119
|
+
zeroAmount: filteredOutZeroAmount,
|
|
120
|
+
missingFields: filteredOutMissingFields,
|
|
121
|
+
invalidDate: filteredOutInvalidDate,
|
|
122
|
+
timeRange: filteredOutTimeRange,
|
|
123
|
+
totalRecords
|
|
124
|
+
});
|
|
96
125
|
return Object.values(transformedData);
|
|
97
126
|
}
|
|
98
127
|
// override this method so that we do not read from the config file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CustomProviderClient.cjs.js","sources":["../../src/cost-clients/CustomProviderClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { getCustomCostsByDateRange } from '../models/CustomCost';\nimport { CACHE_CATEGORY, CLOUD_PROVIDER, PROVIDER_TYPE, GRANULARITY } from '../service/consts';\nimport {\n getDailyPeriodStringsForOneMonth,\n getDefaultCacheTTL,\n getReportsFromCache,\n setReportsToCache,\n} from '../service/functions';\nimport { ClientResponse, CloudProviderError, CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { CustomCostRecordSchema } from '../schemas/CustomProviderBilling';\nimport { ZodError } from 'zod';\n\nexport class CustomProviderClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new CustomProviderClient(CLOUD_PROVIDER.CUSTOM, config, database, cache, logger);\n }\n\n protected async initCloudClient(_config: Config): Promise<any> {\n return null;\n }\n\n protected async fetchCosts(_integrationConfig: Config | null, _client: any, query: CostQuery): Promise<any> {\n const records = getCustomCostsByDateRange(\n this.database,\n moment(parseInt(query.startTime, 10)).toDate(),\n moment(parseInt(query.endTime, 10)).toDate(),\n );\n return records;\n }\n\n protected async transformCostsData(\n _subAccountConfig: Config | null,\n query: CostQuery,\n costResponse: any,\n ): Promise<Report[]> {\n if (Array.isArray(costResponse)) {\n try {\n costResponse.forEach((record: any) => CustomCostRecordSchema.parse(record));\n this.logger.debug(`Custom cost records validation passed for ${costResponse.length} records`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Custom cost records validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n }\n\n const transformedData = reduce(\n costResponse,\n (accumulator: { [key: string]: Report }, record) => {\n let periodFormat = 'YYYY-MM';\n if (query.granularity === GRANULARITY.DAILY) {\n periodFormat = 'YYYY-MM-DD';\n }\n\n const keyName = `${record.provider}-${record.account}-${record.service}`;\n\n // make it compatible with SQLite database\n if (typeof record.tags === 'string') {\n try {\n record.tags = JSON.parse(record.tags);\n } catch (error) {\n this.logger.error(`Failed to parse tags for custom cost ${keyName}, tags: ${record.tags}`);\n record.tags = {};\n }\n }\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n account: record.account,\n service: record.service,\n category: record.category,\n provider: record.provider,\n providerType: PROVIDER_TYPE.CUSTOM,\n reports: {},\n };\n }\n\n // if custom costs with same provider+account+service values, but contain different tags\n // we merge these tags, but the tag with same key will be overriden\n accumulator[keyName] = { ...accumulator[keyName], ...record.tags };\n\n const cost = parseFloat(record.cost);\n if (query.granularity === GRANULARITY.MONTHLY) {\n const period = moment(record.usage_month.toString(), 'YYYYMM').format(periodFormat);\n accumulator[keyName].reports[period] = cost;\n } else {\n if (record.amortization_mode === 'average') {\n // calculate the average daily cost\n const periods = getDailyPeriodStringsForOneMonth(record.usage_month);\n const averageCost = parseFloat(record.cost) / periods.length;\n periods.forEach(period => {\n accumulator[keyName].reports[period] = averageCost;\n });\n } else if (record.amortization_mode === 'start_day') {\n const period = moment(record.usage_month.toString(), 'YYYYMM').startOf('month').format(periodFormat);\n accumulator[keyName].reports[period] = cost;\n } else {\n const period = moment(record.usage_month.toString(), 'YYYYMM').endOf('month').format(periodFormat);\n accumulator[keyName].reports[period] = cost;\n }\n }\n\n return accumulator;\n },\n {},\n );\n\n return Object.values(transformedData);\n }\n\n // override this method so that we do not read from the config file\n async getCostReports(query: CostQuery): Promise<ClientResponse> {\n const results: Report[] = [];\n const errors: CloudProviderError[] = [];\n\n // first check if there is any cached\n const cachedCosts = await getReportsFromCache(this.cache, this.provider, 'custom', query);\n if (cachedCosts) {\n this.logger.debug(`${this.provider} costs from cache`);\n cachedCosts.forEach(cost => {\n results.push(cost);\n });\n }\n\n try {\n const costResponse = await this.fetchCosts(null, null, query);\n\n const transformedReports = await this.transformCostsData(null, query, costResponse);\n\n // cache the results\n await setReportsToCache(\n this.cache,\n transformedReports,\n this.provider,\n 'custom',\n query,\n getDefaultCacheTTL(CACHE_CATEGORY.COSTS, this.provider),\n );\n\n transformedReports.forEach((value: any) => {\n results.push(value);\n });\n } catch (e) {\n this.logger.error(e);\n errors.push({\n provider: this.provider,\n name: this.provider,\n error: e.message,\n });\n }\n return {\n reports: results,\n errors: errors,\n };\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","getCustomCostsByDateRange","moment","CustomCostRecordSchema","ZodError","reduce","GRANULARITY","PROVIDER_TYPE","getDailyPeriodStringsForOneMonth","getReportsFromCache","setReportsToCache","getDefaultCacheTTL","CACHE_CATEGORY"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CustomProviderClient.cjs.js","sources":["../../src/cost-clients/CustomProviderClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { getCustomCostsByDateRange } from '../models/CustomCost';\nimport { CACHE_CATEGORY, CLOUD_PROVIDER, PROVIDER_TYPE, GRANULARITY } from '../service/consts';\nimport {\n getDailyPeriodStringsForOneMonth,\n getDefaultCacheTTL,\n getReportsFromCache,\n setReportsToCache,\n} from '../service/functions';\nimport { ClientResponse, CloudProviderError, CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { CustomCostRecordSchema } from '../schemas/CustomProviderBilling';\nimport { ZodError } from 'zod';\n\nexport class CustomProviderClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new CustomProviderClient(CLOUD_PROVIDER.CUSTOM, config, database, cache, logger);\n }\n\n protected async initCloudClient(_config: Config): Promise<any> {\n return null;\n }\n\n protected async fetchCosts(_integrationConfig: Config | null, _client: any, query: CostQuery): Promise<any> {\n const records = getCustomCostsByDateRange(\n this.database,\n moment(parseInt(query.startTime, 10)).toDate(),\n moment(parseInt(query.endTime, 10)).toDate(),\n );\n return records;\n }\n\n protected async transformCostsData(\n _subAccountConfig: Config | null,\n query: CostQuery,\n costResponse: any,\n ): Promise<Report[]> {\n if (Array.isArray(costResponse)) {\n try {\n costResponse.forEach((record: any) => CustomCostRecordSchema.parse(record));\n this.logger.debug(`Custom cost records validation passed for ${costResponse.length} records`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Custom cost records validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n }\n\n // Initialize tracking variables\n let processedRecords = 0;\n let filteredOutZeroAmount = 0;\n let filteredOutMissingFields = 0;\n const filteredOutInvalidDate = 0;\n const filteredOutTimeRange = 0;\n const uniqueKeys = new Set<string>();\n const totalRecords = Array.isArray(costResponse) ? costResponse.length : 0;\n\n const transformedData = reduce(\n costResponse,\n (accumulator: { [key: string]: Report }, record) => {\n // Check for missing fields\n if (\n !record.provider ||\n !record.account ||\n !record.service ||\n !record.category ||\n record.cost === undefined ||\n record.cost === null ||\n !record.usage_month\n ) {\n filteredOutMissingFields++;\n return accumulator;\n }\n\n const cost = parseFloat(record.cost);\n\n // Check for zero amount\n if (cost === 0) {\n filteredOutZeroAmount++;\n return accumulator;\n }\n\n let periodFormat = 'YYYY-MM';\n if (query.granularity === GRANULARITY.DAILY) {\n periodFormat = 'YYYY-MM-DD';\n }\n\n const keyName = `${record.provider}-${record.account}-${record.service}`;\n\n // make it compatible with SQLite database\n if (typeof record.tags === 'string') {\n try {\n record.tags = JSON.parse(record.tags);\n } catch (error) {\n this.logger.error(`Failed to parse tags for custom cost ${keyName}, tags: ${record.tags}`);\n record.tags = {};\n }\n }\n\n if (!accumulator[keyName]) {\n uniqueKeys.add(keyName);\n accumulator[keyName] = {\n id: keyName,\n account: record.account,\n service: record.service,\n category: record.category,\n provider: record.provider,\n providerType: PROVIDER_TYPE.CUSTOM,\n reports: {},\n };\n }\n\n // if custom costs with same provider+account+service values, but contain different tags\n // we merge these tags, but the tag with same key will be overriden\n accumulator[keyName] = { ...accumulator[keyName], ...record.tags };\n\n if (query.granularity === GRANULARITY.MONTHLY) {\n const period = moment(record.usage_month.toString(), 'YYYYMM').format(periodFormat);\n accumulator[keyName].reports[period] = cost;\n processedRecords++;\n } else {\n if (record.amortization_mode === 'average') {\n // calculate the average daily cost\n const periods = getDailyPeriodStringsForOneMonth(record.usage_month);\n const averageCost = parseFloat(record.cost) / periods.length;\n periods.forEach(period => {\n accumulator[keyName].reports[period] = averageCost;\n });\n processedRecords += periods.length;\n } else if (record.amortization_mode === 'start_day') {\n const period = moment(record.usage_month.toString(), 'YYYYMM').startOf('month').format(periodFormat);\n accumulator[keyName].reports[period] = cost;\n processedRecords++;\n } else {\n const period = moment(record.usage_month.toString(), 'YYYYMM').endOf('month').format(periodFormat);\n accumulator[keyName].reports[period] = cost;\n processedRecords++;\n }\n }\n\n return accumulator;\n },\n {},\n );\n\n this.logTransformationSummary({\n processed: processedRecords,\n uniqueReports: uniqueKeys.size,\n zeroAmount: filteredOutZeroAmount,\n missingFields: filteredOutMissingFields,\n invalidDate: filteredOutInvalidDate,\n timeRange: filteredOutTimeRange,\n totalRecords,\n });\n\n return Object.values(transformedData);\n }\n\n // override this method so that we do not read from the config file\n async getCostReports(query: CostQuery): Promise<ClientResponse> {\n const results: Report[] = [];\n const errors: CloudProviderError[] = [];\n\n // first check if there is any cached\n const cachedCosts = await getReportsFromCache(this.cache, this.provider, 'custom', query);\n if (cachedCosts) {\n this.logger.debug(`${this.provider} costs from cache`);\n cachedCosts.forEach(cost => {\n results.push(cost);\n });\n }\n\n try {\n const costResponse = await this.fetchCosts(null, null, query);\n\n const transformedReports = await this.transformCostsData(null, query, costResponse);\n\n // cache the results\n await setReportsToCache(\n this.cache,\n transformedReports,\n this.provider,\n 'custom',\n query,\n getDefaultCacheTTL(CACHE_CATEGORY.COSTS, this.provider),\n );\n\n transformedReports.forEach((value: any) => {\n results.push(value);\n });\n } catch (e) {\n this.logger.error(e);\n errors.push({\n provider: this.provider,\n name: this.provider,\n error: e.message,\n });\n }\n return {\n reports: results,\n errors: errors,\n };\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","getCustomCostsByDateRange","moment","CustomCostRecordSchema","ZodError","reduce","GRANULARITY","PROVIDER_TYPE","getDailyPeriodStringsForOneMonth","getReportsFromCache","setReportsToCache","getDefaultCacheTTL","CACHE_CATEGORY"],"mappings":"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"}
|
|
@@ -189,22 +189,48 @@ class DatadogClient extends InfraWalletClient.InfraWalletClient {
|
|
|
189
189
|
const [k, v] = tag.split(":");
|
|
190
190
|
tagKeyValues[k.trim()] = v.trim();
|
|
191
191
|
});
|
|
192
|
+
let processedRecords = 0;
|
|
193
|
+
let filteredOutZeroAmount = 0;
|
|
194
|
+
let filteredOutMissingFields = 0;
|
|
195
|
+
let filteredOutInvalidDate = 0;
|
|
196
|
+
const filteredOutTimeRange = 0;
|
|
197
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
198
|
+
const totalRecords = costResponse?.length || 0;
|
|
192
199
|
const transformedData = lodash.reduce(
|
|
193
200
|
costResponse,
|
|
194
201
|
(accumulator, costByOrg) => {
|
|
195
202
|
const account = costByOrg.orgName;
|
|
196
203
|
const charges = costByOrg.charges;
|
|
204
|
+
if (!account || !costByOrg.date) {
|
|
205
|
+
filteredOutMissingFields++;
|
|
206
|
+
return accumulator;
|
|
207
|
+
}
|
|
197
208
|
let periodFormat = "YYYY-MM";
|
|
198
209
|
if (query.granularity === consts.GRANULARITY.DAILY) {
|
|
199
210
|
periodFormat = "YYYY-MM-DD";
|
|
200
211
|
}
|
|
201
|
-
const
|
|
212
|
+
const dateObj = moment__default.default(costByOrg.date);
|
|
213
|
+
if (!dateObj.isValid()) {
|
|
214
|
+
filteredOutInvalidDate++;
|
|
215
|
+
return accumulator;
|
|
216
|
+
}
|
|
217
|
+
const period = dateObj.format(periodFormat);
|
|
202
218
|
if (charges) {
|
|
203
219
|
charges.forEach((charge) => {
|
|
204
220
|
const productName = charge.productName;
|
|
205
221
|
const cost = charge.cost;
|
|
222
|
+
if (!productName || cost === void 0 || cost === null) {
|
|
223
|
+
filteredOutMissingFields++;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const amount = functions.parseCost(cost);
|
|
227
|
+
if (amount === 0) {
|
|
228
|
+
filteredOutZeroAmount++;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
206
231
|
const keyName = `${account}->${productName} (${charge.chargeType})`;
|
|
207
232
|
if (!accumulator[keyName]) {
|
|
233
|
+
uniqueKeys.add(keyName);
|
|
208
234
|
accumulator[keyName] = {
|
|
209
235
|
id: keyName,
|
|
210
236
|
account: `${this.provider}/${account}`,
|
|
@@ -216,13 +242,23 @@ class DatadogClient extends InfraWalletClient.InfraWalletClient {
|
|
|
216
242
|
...tagKeyValues
|
|
217
243
|
};
|
|
218
244
|
}
|
|
219
|
-
accumulator[keyName].reports[period] =
|
|
245
|
+
accumulator[keyName].reports[period] = amount;
|
|
246
|
+
processedRecords++;
|
|
220
247
|
});
|
|
221
248
|
}
|
|
222
249
|
return accumulator;
|
|
223
250
|
},
|
|
224
251
|
{}
|
|
225
252
|
);
|
|
253
|
+
this.logTransformationSummary({
|
|
254
|
+
processed: processedRecords,
|
|
255
|
+
uniqueReports: uniqueKeys.size,
|
|
256
|
+
zeroAmount: filteredOutZeroAmount,
|
|
257
|
+
missingFields: filteredOutMissingFields,
|
|
258
|
+
invalidDate: filteredOutInvalidDate,
|
|
259
|
+
timeRange: filteredOutTimeRange,
|
|
260
|
+
totalRecords
|
|
261
|
+
});
|
|
226
262
|
return Object.values(transformedData);
|
|
227
263
|
}
|
|
228
264
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatadogClient.cjs.js","sources":["../../src/cost-clients/DatadogClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { v2 as datadogApiV2, client as datadogClient } from '@datadog/datadog-api-client';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { CLOUD_PROVIDER, GRANULARITY, PROVIDER_TYPE } from '../service/consts';\nimport { parseCost } from '../service/functions';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { DatadogCostByOrgResponseSchema } from '../schemas/DatadogBilling';\nimport { ZodError } from 'zod';\n\nexport class DatadogClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new DatadogClient(CLOUD_PROVIDER.DATADOG, config, database, cache, logger);\n }\n\n protected convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n // Datadog doesn't have this documented offically, raise a PR if any service is missed\n const aliases = new Map<string, string>([\n ['apm_host', 'APM Hosts'],\n ['apm_host_enterprise', 'APM Enterprise Hosts'],\n ['application_vulnerability_management_oss_host', 'Application Security - SCA Host'],\n ['application_security_host', 'ASM - Threat Management Hosts'],\n ['audit_trail', 'Audit Trail'],\n ['ci_pipeline', 'CI Visibility Committers'],\n ['ci_pipeline_indexed_spans', 'CI Visibility Spans'],\n ['cloud_cost_management', 'Cloud Cost Hosts'],\n ['cspm_container', 'Cloud Security Management Containers Pro'],\n ['cspm_host', 'Cloud Security Management Hosts Pro'],\n ['csm_host_pro', 'Cloud Security Management Hosts Pro'],\n ['cws_host', 'Cloud Workload Security Hosts'],\n ['infra_container', 'Containers'],\n ['infra_container_excl_agent', 'Containers'],\n ['timeseries', 'Custom Metrics'],\n ['error_tracking', 'Error Tracking'],\n ['incident_management', 'Incident Management'],\n ['logs_indexed_15day', 'Indexed Logs (15-day Retention)'],\n ['logs_indexed_180day', 'Indexed Logs (180-day Retention)'],\n ['logs_indexed_1day', 'Indexed Logs (1-day Retention)'],\n ['logs_indexed_30day', 'Indexed Logs (30-day Retention)'],\n ['logs_indexed_360day', 'Indexed Logs (360-day Retention)'],\n ['logs_indexed_3day', 'Indexed Logs (3-day Retention)'],\n ['logs_indexed_45day', 'Indexed Logs (45-day Retention)'],\n ['logs_indexed_60day', 'Indexed Logs (60-day Retention)'],\n ['logs_indexed_7day', 'Indexed Logs (7-day Retention)'],\n ['logs_indexed_90day', 'Indexed Logs (90-day Retention)'],\n ['apm_trace_search', 'Indexed Spans'],\n ['infra_host', 'Infra Hosts'],\n ['logs_ingested', 'Ingested Logs'],\n ['ingested_spans', 'Ingested Spans'],\n ['iot', 'IoT Devices'],\n ['npm_host', 'Network Hosts'],\n ['prof_container', 'Profiled Containers'],\n ['prof_host', 'Profiled Hosts'],\n ['rum_lite', 'RUM Sessions'],\n ['rum_replay', 'RUM with Session Replay Sessions'],\n ['siem_indexed', 'Security Analyzed and Indexed Logs'],\n ['sensitive_data_scanner', 'Sensitive Data Scanner'],\n ['serverless_apps', 'Serverless App Instances'],\n ['serverless_apm', 'Serverless Traced Invocations'],\n ['serverless_infra', 'Serverless Workload Functions'],\n ['siem', 'SIEM - Analyzed Logs'],\n ['synthetics_api_tests', 'Synthetics API Test Runs'],\n ['synthetics_browser_checks', 'Synthetics Browser Test Runs'],\n ['ci_testing', 'Test Visibility Committers'],\n ['ci_test_indexed_spans', 'Test Visibility Spans'],\n ]);\n\n if (aliases.has(convertedName)) {\n convertedName = aliases.get(convertedName) || convertedName;\n }\n\n return `${this.provider}/${convertedName}`;\n }\n\n protected async initCloudClient(integrationConfig: any): Promise<any> {\n const apiKey = integrationConfig.getString('apiKey');\n const applicationKey = integrationConfig.getString('applicationKey');\n const ddSite = integrationConfig.getString('ddSite');\n const configuration = datadogClient.createConfiguration({\n baseServer: new datadogClient.BaseServerConfiguration(ddSite, {}),\n authMethods: {\n apiKeyAuth: apiKey,\n appKeyAuth: applicationKey,\n },\n });\n const client = new datadogApiV2.UsageMeteringApi(configuration);\n return client;\n }\n\n protected async fetchCosts(integrationConfig: Config, client: any, query: CostQuery): Promise<any> {\n const costData: datadogApiV2.CostByOrg[] = [];\n const startTime = moment(parseInt(query.startTime, 10));\n const endTime = moment(parseInt(query.endTime, 10));\n const firstDayOfLastMonth = moment().subtract(1, 'M').startOf('M');\n\n // check if costs prior to 2 months ago are in query, if yes, use historical_cost API\n // https://docs.datadoghq.com/api/latest/usage-metering/#get-historical-cost-across-your-account\n if (startTime.isBefore(firstDayOfLastMonth)) {\n const historicalCost: datadogApiV2.CostByOrgResponse = await client.getHistoricalCostByOrg({\n startMonth: startTime,\n endMonth: firstDayOfLastMonth.subtract(1, 'd'),\n view: 'sub-org',\n });\n\n try {\n DatadogCostByOrgResponseSchema.parse(historicalCost);\n this.logger.debug(`Datadog historical cost response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Datadog historical cost response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n if (historicalCost.data) {\n costData.push(...historicalCost.data);\n }\n }\n\n // check if current/last month costs are in query, if yes, use estimated_cost API\n // https://docs.datadoghq.com/api/latest/usage-metering/#get-estimated-cost-across-your-account\n if (endTime.isSameOrAfter(firstDayOfLastMonth)) {\n let estimatedCostStartTime = startTime;\n if (startTime.isBefore(firstDayOfLastMonth)) {\n estimatedCostStartTime = firstDayOfLastMonth;\n }\n\n const estimatedCost: datadogApiV2.CostByOrgResponse = await client.getEstimatedCostByOrg({\n startMonth: estimatedCostStartTime,\n endMonth: endTime,\n view: 'sub-org',\n });\n\n try {\n DatadogCostByOrgResponseSchema.parse(estimatedCost);\n this.logger.debug(`Datadog estimated cost response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Datadog estimated cost response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n if (estimatedCost.data) {\n costData.push(...estimatedCost.data);\n }\n }\n\n const costs: any[] = [];\n\n if (query.granularity === GRANULARITY.MONTHLY) {\n costData.forEach(costByOrg => {\n const orgName = costByOrg.attributes?.orgName as string;\n if (!this.evaluateIntegrationFilters(orgName, integrationConfig)) {\n return;\n }\n\n costs.push({\n orgName: orgName,\n date: costByOrg.attributes?.date,\n // only keep cost breakdown\n charges: costByOrg.attributes?.charges?.filter(charge => charge.chargeType !== 'total'),\n });\n });\n } else {\n // Datadog doesn't provide daily costs based on usage, so we allocate monthly costs evenly by day\n costData.forEach(costByOrg => {\n const orgName = costByOrg.attributes?.orgName as string;\n if (!this.evaluateIntegrationFilters(orgName, integrationConfig)) {\n return;\n }\n\n const daysInMonth = moment(costByOrg.attributes?.date).daysInMonth();\n costByOrg.attributes?.charges?.forEach(charge => {\n if (charge.chargeType === 'total') {\n // only keep cost breakdown\n return;\n }\n\n for (let i = 0; i < daysInMonth; i++) {\n const dailyCost = {\n orgName: orgName,\n date: moment(costByOrg.attributes?.date).add(i, 'd'),\n charges: [\n {\n productName: charge.productName,\n cost: (charge.cost || 0) / daysInMonth,\n chargeType: charge.chargeType,\n },\n ],\n };\n costs.push(dailyCost);\n }\n });\n });\n }\n\n return costs;\n }\n\n protected async transformCostsData(subAccountConfig: Config, query: CostQuery, costResponse: any): Promise<Report[]> {\n const tags = subAccountConfig.getOptionalStringArray('tags');\n const tagKeyValues: { [key: string]: string } = {};\n tags?.forEach(tag => {\n const [k, v] = tag.split(':');\n tagKeyValues[k.trim()] = v.trim();\n });\n\n const transformedData = reduce(\n costResponse,\n (accumulator: { [key: string]: Report }, costByOrg) => {\n const account = costByOrg.orgName;\n const charges = costByOrg.charges;\n\n let periodFormat = 'YYYY-MM';\n if (query.granularity === GRANULARITY.DAILY) {\n periodFormat = 'YYYY-MM-DD';\n }\n const period = moment(costByOrg.date).format(periodFormat);\n\n if (charges) {\n charges.forEach((charge: datadogApiV2.ChargebackBreakdown) => {\n const productName = charge.productName;\n const cost = charge.cost;\n const keyName = `${account}->${productName} (${charge.chargeType})`;\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${account}`,\n service: `${this.convertServiceName(productName as string)} (${charge.chargeType})`,\n category: 'Observability',\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...tagKeyValues,\n };\n }\n\n accumulator[keyName].reports[period] = parseCost(cost);\n });\n }\n\n return accumulator;\n },\n {},\n );\n\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","datadogClient","datadogApiV2","moment","DatadogCostByOrgResponseSchema","ZodError","GRANULARITY","reduce","PROVIDER_TYPE","parseCost"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DatadogClient.cjs.js","sources":["../../src/cost-clients/DatadogClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { v2 as datadogApiV2, client as datadogClient } from '@datadog/datadog-api-client';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { CLOUD_PROVIDER, GRANULARITY, PROVIDER_TYPE } from '../service/consts';\nimport { parseCost } from '../service/functions';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { DatadogCostByOrgResponseSchema } from '../schemas/DatadogBilling';\nimport { ZodError } from 'zod';\n\nexport class DatadogClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new DatadogClient(CLOUD_PROVIDER.DATADOG, config, database, cache, logger);\n }\n\n protected convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n // Datadog doesn't have this documented offically, raise a PR if any service is missed\n const aliases = new Map<string, string>([\n ['apm_host', 'APM Hosts'],\n ['apm_host_enterprise', 'APM Enterprise Hosts'],\n ['application_vulnerability_management_oss_host', 'Application Security - SCA Host'],\n ['application_security_host', 'ASM - Threat Management Hosts'],\n ['audit_trail', 'Audit Trail'],\n ['ci_pipeline', 'CI Visibility Committers'],\n ['ci_pipeline_indexed_spans', 'CI Visibility Spans'],\n ['cloud_cost_management', 'Cloud Cost Hosts'],\n ['cspm_container', 'Cloud Security Management Containers Pro'],\n ['cspm_host', 'Cloud Security Management Hosts Pro'],\n ['csm_host_pro', 'Cloud Security Management Hosts Pro'],\n ['cws_host', 'Cloud Workload Security Hosts'],\n ['infra_container', 'Containers'],\n ['infra_container_excl_agent', 'Containers'],\n ['timeseries', 'Custom Metrics'],\n ['error_tracking', 'Error Tracking'],\n ['incident_management', 'Incident Management'],\n ['logs_indexed_15day', 'Indexed Logs (15-day Retention)'],\n ['logs_indexed_180day', 'Indexed Logs (180-day Retention)'],\n ['logs_indexed_1day', 'Indexed Logs (1-day Retention)'],\n ['logs_indexed_30day', 'Indexed Logs (30-day Retention)'],\n ['logs_indexed_360day', 'Indexed Logs (360-day Retention)'],\n ['logs_indexed_3day', 'Indexed Logs (3-day Retention)'],\n ['logs_indexed_45day', 'Indexed Logs (45-day Retention)'],\n ['logs_indexed_60day', 'Indexed Logs (60-day Retention)'],\n ['logs_indexed_7day', 'Indexed Logs (7-day Retention)'],\n ['logs_indexed_90day', 'Indexed Logs (90-day Retention)'],\n ['apm_trace_search', 'Indexed Spans'],\n ['infra_host', 'Infra Hosts'],\n ['logs_ingested', 'Ingested Logs'],\n ['ingested_spans', 'Ingested Spans'],\n ['iot', 'IoT Devices'],\n ['npm_host', 'Network Hosts'],\n ['prof_container', 'Profiled Containers'],\n ['prof_host', 'Profiled Hosts'],\n ['rum_lite', 'RUM Sessions'],\n ['rum_replay', 'RUM with Session Replay Sessions'],\n ['siem_indexed', 'Security Analyzed and Indexed Logs'],\n ['sensitive_data_scanner', 'Sensitive Data Scanner'],\n ['serverless_apps', 'Serverless App Instances'],\n ['serverless_apm', 'Serverless Traced Invocations'],\n ['serverless_infra', 'Serverless Workload Functions'],\n ['siem', 'SIEM - Analyzed Logs'],\n ['synthetics_api_tests', 'Synthetics API Test Runs'],\n ['synthetics_browser_checks', 'Synthetics Browser Test Runs'],\n ['ci_testing', 'Test Visibility Committers'],\n ['ci_test_indexed_spans', 'Test Visibility Spans'],\n ]);\n\n if (aliases.has(convertedName)) {\n convertedName = aliases.get(convertedName) || convertedName;\n }\n\n return `${this.provider}/${convertedName}`;\n }\n\n protected async initCloudClient(integrationConfig: any): Promise<any> {\n const apiKey = integrationConfig.getString('apiKey');\n const applicationKey = integrationConfig.getString('applicationKey');\n const ddSite = integrationConfig.getString('ddSite');\n const configuration = datadogClient.createConfiguration({\n baseServer: new datadogClient.BaseServerConfiguration(ddSite, {}),\n authMethods: {\n apiKeyAuth: apiKey,\n appKeyAuth: applicationKey,\n },\n });\n const client = new datadogApiV2.UsageMeteringApi(configuration);\n return client;\n }\n\n protected async fetchCosts(integrationConfig: Config, client: any, query: CostQuery): Promise<any> {\n const costData: datadogApiV2.CostByOrg[] = [];\n const startTime = moment(parseInt(query.startTime, 10));\n const endTime = moment(parseInt(query.endTime, 10));\n const firstDayOfLastMonth = moment().subtract(1, 'M').startOf('M');\n\n // check if costs prior to 2 months ago are in query, if yes, use historical_cost API\n // https://docs.datadoghq.com/api/latest/usage-metering/#get-historical-cost-across-your-account\n if (startTime.isBefore(firstDayOfLastMonth)) {\n const historicalCost: datadogApiV2.CostByOrgResponse = await client.getHistoricalCostByOrg({\n startMonth: startTime,\n endMonth: firstDayOfLastMonth.subtract(1, 'd'),\n view: 'sub-org',\n });\n\n try {\n DatadogCostByOrgResponseSchema.parse(historicalCost);\n this.logger.debug(`Datadog historical cost response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Datadog historical cost response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n if (historicalCost.data) {\n costData.push(...historicalCost.data);\n }\n }\n\n // check if current/last month costs are in query, if yes, use estimated_cost API\n // https://docs.datadoghq.com/api/latest/usage-metering/#get-estimated-cost-across-your-account\n if (endTime.isSameOrAfter(firstDayOfLastMonth)) {\n let estimatedCostStartTime = startTime;\n if (startTime.isBefore(firstDayOfLastMonth)) {\n estimatedCostStartTime = firstDayOfLastMonth;\n }\n\n const estimatedCost: datadogApiV2.CostByOrgResponse = await client.getEstimatedCostByOrg({\n startMonth: estimatedCostStartTime,\n endMonth: endTime,\n view: 'sub-org',\n });\n\n try {\n DatadogCostByOrgResponseSchema.parse(estimatedCost);\n this.logger.debug(`Datadog estimated cost response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Datadog estimated cost response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n if (estimatedCost.data) {\n costData.push(...estimatedCost.data);\n }\n }\n\n const costs: any[] = [];\n\n if (query.granularity === GRANULARITY.MONTHLY) {\n costData.forEach(costByOrg => {\n const orgName = costByOrg.attributes?.orgName as string;\n if (!this.evaluateIntegrationFilters(orgName, integrationConfig)) {\n return;\n }\n\n costs.push({\n orgName: orgName,\n date: costByOrg.attributes?.date,\n // only keep cost breakdown\n charges: costByOrg.attributes?.charges?.filter(charge => charge.chargeType !== 'total'),\n });\n });\n } else {\n // Datadog doesn't provide daily costs based on usage, so we allocate monthly costs evenly by day\n costData.forEach(costByOrg => {\n const orgName = costByOrg.attributes?.orgName as string;\n if (!this.evaluateIntegrationFilters(orgName, integrationConfig)) {\n return;\n }\n\n const daysInMonth = moment(costByOrg.attributes?.date).daysInMonth();\n costByOrg.attributes?.charges?.forEach(charge => {\n if (charge.chargeType === 'total') {\n // only keep cost breakdown\n return;\n }\n\n for (let i = 0; i < daysInMonth; i++) {\n const dailyCost = {\n orgName: orgName,\n date: moment(costByOrg.attributes?.date).add(i, 'd'),\n charges: [\n {\n productName: charge.productName,\n cost: (charge.cost || 0) / daysInMonth,\n chargeType: charge.chargeType,\n },\n ],\n };\n costs.push(dailyCost);\n }\n });\n });\n }\n\n return costs;\n }\n\n protected async transformCostsData(subAccountConfig: Config, query: CostQuery, costResponse: any): Promise<Report[]> {\n const tags = subAccountConfig.getOptionalStringArray('tags');\n const tagKeyValues: { [key: string]: string } = {};\n tags?.forEach(tag => {\n const [k, v] = tag.split(':');\n tagKeyValues[k.trim()] = v.trim();\n });\n\n // Initialize tracking variables\n let processedRecords = 0;\n let filteredOutZeroAmount = 0;\n let filteredOutMissingFields = 0;\n let filteredOutInvalidDate = 0;\n const filteredOutTimeRange = 0;\n const uniqueKeys = new Set<string>();\n const totalRecords = costResponse?.length || 0;\n\n const transformedData = reduce(\n costResponse,\n (accumulator: { [key: string]: Report }, costByOrg) => {\n const account = costByOrg.orgName;\n const charges = costByOrg.charges;\n\n // Check for missing fields\n if (!account || !costByOrg.date) {\n filteredOutMissingFields++;\n return accumulator;\n }\n\n let periodFormat = 'YYYY-MM';\n if (query.granularity === GRANULARITY.DAILY) {\n periodFormat = 'YYYY-MM-DD';\n }\n\n const dateObj = moment(costByOrg.date);\n if (!dateObj.isValid()) {\n filteredOutInvalidDate++;\n return accumulator;\n }\n\n const period = dateObj.format(periodFormat);\n\n if (charges) {\n charges.forEach((charge: datadogApiV2.ChargebackBreakdown) => {\n const productName = charge.productName;\n const cost = charge.cost;\n\n // Check for missing fields\n if (!productName || cost === undefined || cost === null) {\n filteredOutMissingFields++;\n return;\n }\n\n const amount = parseCost(cost);\n\n // Check for zero amount\n if (amount === 0) {\n filteredOutZeroAmount++;\n return;\n }\n\n const keyName = `${account}->${productName} (${charge.chargeType})`;\n\n if (!accumulator[keyName]) {\n uniqueKeys.add(keyName);\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${account}`,\n service: `${this.convertServiceName(productName as string)} (${charge.chargeType})`,\n category: 'Observability',\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...tagKeyValues,\n };\n }\n\n accumulator[keyName].reports[period] = amount;\n processedRecords++;\n });\n }\n\n return accumulator;\n },\n {},\n );\n\n this.logTransformationSummary({\n processed: processedRecords,\n uniqueReports: uniqueKeys.size,\n zeroAmount: filteredOutZeroAmount,\n missingFields: filteredOutMissingFields,\n invalidDate: filteredOutInvalidDate,\n timeRange: filteredOutTimeRange,\n totalRecords,\n });\n\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","datadogClient","datadogApiV2","moment","DatadogCostByOrgResponseSchema","ZodError","GRANULARITY","reduce","parseCost","PROVIDER_TYPE"],"mappings":"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"}
|
|
@@ -141,16 +141,42 @@ class ElasticCloudClient extends InfraWalletClient.InfraWalletClient {
|
|
|
141
141
|
async transformCostsData(integrationConfig, query, costResponse) {
|
|
142
142
|
const accountName = integrationConfig.getString("name");
|
|
143
143
|
const tagKeyValues = this.extractConfigTags(integrationConfig);
|
|
144
|
+
let totalRecords = 0;
|
|
145
|
+
if (costResponse?.instanceCosts?.instances?.length) {
|
|
146
|
+
totalRecords += costResponse.instanceCosts.instances.length;
|
|
147
|
+
}
|
|
148
|
+
if (costResponse?.itemCosts?.products?.length) {
|
|
149
|
+
totalRecords += costResponse.itemCosts.products.length;
|
|
150
|
+
}
|
|
151
|
+
if (costResponse?.charts?.data?.length) {
|
|
152
|
+
totalRecords += costResponse.charts.data.length;
|
|
153
|
+
}
|
|
144
154
|
try {
|
|
145
155
|
const reports = /* @__PURE__ */ new Map();
|
|
146
156
|
const periodFormat = this.getPeriodFormat(query);
|
|
157
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
158
|
+
const metrics = {
|
|
159
|
+
processed: 0,
|
|
160
|
+
zeroAmount: 0,
|
|
161
|
+
missingFields: 0,
|
|
162
|
+
invalidDate: 0,
|
|
163
|
+
timeRange: 0
|
|
164
|
+
};
|
|
147
165
|
this.processInstanceCosts(costResponse, reports, accountName, integrationConfig, tagKeyValues);
|
|
148
166
|
this.processChartData(costResponse, reports, periodFormat);
|
|
149
167
|
this.distributeRemainingCosts(costResponse, reports, query);
|
|
150
168
|
const filteredReports = this.prepareReportsForOutput(reports, periodFormat);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
metrics.processed = filteredReports.reduce((sum, report) => sum + Object.keys(report.reports).length, 0);
|
|
170
|
+
filteredReports.forEach((report) => uniqueKeys.add(report.id));
|
|
171
|
+
this.logTransformationSummary({
|
|
172
|
+
processed: metrics.processed,
|
|
173
|
+
uniqueReports: uniqueKeys.size,
|
|
174
|
+
zeroAmount: metrics.zeroAmount,
|
|
175
|
+
missingFields: metrics.missingFields,
|
|
176
|
+
invalidDate: metrics.invalidDate,
|
|
177
|
+
timeRange: metrics.timeRange,
|
|
178
|
+
totalRecords
|
|
179
|
+
});
|
|
154
180
|
return filteredReports;
|
|
155
181
|
} catch (error) {
|
|
156
182
|
this.logger.error(`Error transforming Elastic Cloud cost data: ${error.message}`);
|
|
@@ -220,16 +246,20 @@ class ElasticCloudClient extends InfraWalletClient.InfraWalletClient {
|
|
|
220
246
|
if (!value?.id) continue;
|
|
221
247
|
const instanceKey = `instance-${value.id}`;
|
|
222
248
|
if (reports.has(instanceKey)) {
|
|
223
|
-
|
|
249
|
+
const rawValue = value.value ?? 0;
|
|
250
|
+
this.logger.debug(`Chart value for ${instanceKey} period ${period}: raw=${rawValue}`);
|
|
251
|
+
reports.get(instanceKey).reports[period] = rawValue;
|
|
224
252
|
}
|
|
225
253
|
this.matchValueWithItemReports(value, period, reports);
|
|
226
254
|
}
|
|
227
255
|
}
|
|
228
256
|
matchValueWithItemReports(value, period, reports) {
|
|
229
|
-
if (!value
|
|
257
|
+
if (!value?.name) return;
|
|
230
258
|
for (const [key, report] of reports.entries()) {
|
|
231
259
|
if (key.startsWith("item-") && (report.service && value.name.includes(report.service) || report.productType && value.name.includes(report.productType))) {
|
|
232
|
-
|
|
260
|
+
const rawValue = value.value ?? 0;
|
|
261
|
+
this.logger.debug(`Item value for ${key} period ${period}: raw=${rawValue}`);
|
|
262
|
+
report.reports[period] = rawValue;
|
|
233
263
|
}
|
|
234
264
|
}
|
|
235
265
|
}
|
|
@@ -252,7 +282,9 @@ class ElasticCloudClient extends InfraWalletClient.InfraWalletClient {
|
|
|
252
282
|
this.logger.debug(`Distributed costs across ${distributedItems} periods for items without time series data`);
|
|
253
283
|
}
|
|
254
284
|
distributeItemCost(report, lineItem, query, periodFormat) {
|
|
255
|
-
const
|
|
285
|
+
const rawTotalEcu = lineItem.total_ecu ?? 0;
|
|
286
|
+
const totalCost = rawTotalEcu / 100;
|
|
287
|
+
this.logger.debug(`Distribute item cost for ${report.id}: raw_ecu=${rawTotalEcu}, converted_dollars=${totalCost}`);
|
|
256
288
|
if (totalCost <= 0) return 0;
|
|
257
289
|
const startMonth = moment__default.default(parseInt(query.startTime, 10));
|
|
258
290
|
const endMonth = moment__default.default(parseInt(query.endTime, 10));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElasticCloudClient.cjs.js","sources":["../../src/cost-clients/ElasticCloudClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport moment from 'moment';\nimport {\n GetChartsRequestSchema,\n ChartsResponseSchema,\n InstancesResponseSchema,\n ItemsResponseSchema,\n} from '../schemas/ElasticBilling';\nimport { ZodError } from 'zod';\nimport { CLOUD_PROVIDER, PROVIDER_TYPE } from '../service/consts';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\n\n/**\n * Client for fetching and processing cost data from Elastic Cloud\n */\nexport class ElasticCloudClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new ElasticCloudClient(CLOUD_PROVIDER.ELASTIC_CLOUD, config, database, cache, logger);\n }\n\n protected convertServiceName(serviceName: string): string {\n return serviceName.startsWith('Elastic')\n ? `${this.provider}/${serviceName.slice('Elastic'.length).trim()}`\n : `${this.provider}/${serviceName}`;\n }\n\n protected async initCloudClient(integrationConfig: Config): Promise<any> {\n const apiKey = integrationConfig.getString('apiKey');\n\n return {\n baseUrl: 'https://billing.elastic-cloud.com',\n headers: {\n Authorization: `ApiKey ${apiKey}`,\n 'Content-Type': 'application/json',\n },\n };\n }\n\n private async fetchWithRetry(url: string, headers: any, maxRetries = 3): Promise<any> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(url, { method: 'GET', headers });\n\n if (response.status === 429 && attempt < maxRetries) {\n const retryAfter = parseInt(response.headers.get('retry-after') ?? '5', 10);\n this.logger.warn(`Rate limited by Elastic Cloud API, retrying after ${retryAfter} seconds...`);\n await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));\n continue;\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Elastic Cloud API error (${response.status}): ${errorText}`);\n }\n\n return await response.json();\n } catch (error) {\n if (attempt < maxRetries) {\n const backoffTime = Math.pow(2, attempt) * 1000;\n this.logger.warn(`Error fetching from Elastic Cloud, retrying in ${backoffTime}ms: ${error.message}`);\n await new Promise(resolve => setTimeout(resolve, backoffTime));\n } else {\n throw error;\n }\n }\n }\n throw new Error(`Failed to fetch from Elastic Cloud API after ${maxRetries} attempts`);\n }\n\n protected async fetchCosts(integrationConfig: Config, client: any, query: CostQuery): Promise<any> {\n const { baseUrl, headers } = client;\n const organizationId = integrationConfig.getString('organizationId');\n // Convert to ISO 8601 format that's compliant with Zod's datetime validation\n // Use the Z suffix instead of +00:00 which may not be recognized by the schema\n const startDate = moment(parseInt(query.startTime, 10)).toISOString();\n const endDate = moment(parseInt(query.endTime, 10)).toISOString();\n const bucketingStrategy = query.granularity.toLowerCase() === 'daily' ? 'daily' : 'monthly';\n\n this.logger.info(\n `Fetching Elastic Cloud cost data from ${startDate} to ${endDate} with ${bucketingStrategy} granularity`,\n );\n\n try {\n const params = {\n from: startDate,\n to: endDate,\n bucketing_strategy: bucketingStrategy,\n };\n\n // The Elastic Cloud API requires ISO format dates, but the zod schemas expect a specific format\n // Instead of modifying the schemas, we'll use the params directly, logging validation issues\n try {\n GetChartsRequestSchema.parse(params);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(\n `Request parameters didn't match schema for GetChartsRequest: ${JSON.stringify(error.errors)}`,\n );\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n this.logger.debug(`Using params directly: ${JSON.stringify(params)}`);\n }\n\n const chartParams = params;\n const instanceParams = { ...params, include_names: true };\n const itemsParams = { from: params.from, to: params.to };\n\n const createQueryString = (queryParams: Record<string, any>) => {\n return Object.entries(queryParams)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)\n .join('&');\n };\n\n const [instanceCostsResponse, itemCostsResponse, chartsResponse] = await Promise.all([\n this.fetchWithRetry(\n `${baseUrl}/api/v2/billing/organizations/${organizationId}/costs/instances?${createQueryString(instanceParams)}`,\n headers,\n ).then(data => {\n const validationResult = InstancesResponseSchema.safeParse(data);\n if (!validationResult.success) {\n this.logger.warn(`Response validation failed for instance costs: ${validationResult.error.message}`);\n } else {\n this.logger.debug(`Response validation passed for instance costs`);\n }\n this.logger.debug(`Received instance costs data with ${data?.instances?.length ?? 0} instances`);\n return data;\n }),\n\n this.fetchWithRetry(\n `${baseUrl}/api/v2/billing/organizations/${organizationId}/costs/items?${createQueryString(itemsParams)}`,\n headers,\n ).then(data => {\n const validationResult = ItemsResponseSchema.safeParse(data);\n if (!validationResult.success) {\n this.logger.warn(`Response validation failed for item costs: ${validationResult.error.message}`);\n } else {\n this.logger.debug(`Response validation passed for item costs`);\n }\n this.logger.debug(`Received item costs data with ${data?.products?.length ?? 0} products`);\n return data;\n }),\n\n this.fetchWithRetry(\n `${baseUrl}/api/v2/billing/organizations/${organizationId}/charts?${createQueryString(chartParams)}`,\n headers,\n ).then(data => {\n const validationResult = ChartsResponseSchema.safeParse(data);\n if (!validationResult.success) {\n this.logger.warn(`Response validation failed for charts data: ${validationResult.error.message}`);\n } else {\n this.logger.debug(`Response validation passed for charts data`);\n }\n this.logger.debug(`Received charts data with ${data?.data?.length ?? 0} data points`);\n return data;\n }),\n ]);\n\n return {\n instanceCosts: instanceCostsResponse,\n itemCosts: itemCostsResponse,\n charts: chartsResponse,\n };\n } catch (error) {\n this.logger.error(`Error fetching Elastic Cloud costs: ${error.message}`);\n throw error;\n }\n }\n\n protected async transformCostsData(\n integrationConfig: Config,\n query: CostQuery,\n costResponse: any,\n ): Promise<Report[]> {\n const accountName = integrationConfig.getString('name');\n const tagKeyValues = this.extractConfigTags(integrationConfig);\n\n try {\n const reports = new Map();\n const periodFormat = this.getPeriodFormat(query);\n\n this.processInstanceCosts(costResponse, reports, accountName, integrationConfig, tagKeyValues);\n this.processChartData(costResponse, reports, periodFormat);\n this.distributeRemainingCosts(costResponse, reports, query);\n\n const filteredReports = this.prepareReportsForOutput(reports, periodFormat);\n\n this.logger.info(\n `Returning ${filteredReports.length} reports with ${\n Object.keys(filteredReports.reduce((acc, r) => ({ ...acc, ...r.reports }), {})).length\n } periods`,\n );\n\n return filteredReports;\n } catch (error) {\n this.logger.error(`Error transforming Elastic Cloud cost data: ${error.message}`);\n throw error;\n }\n }\n\n private extractConfigTags(integrationConfig: Config): Record<string, string> {\n const tags = integrationConfig.getOptionalStringArray('tags') ?? [];\n const tagKeyValues: Record<string, string> = {};\n\n tags.forEach(tag => {\n const [k, v] = tag.split(':').map(part => part.trim());\n tagKeyValues[k] = v;\n });\n\n return tagKeyValues;\n }\n\n // Determine period format based on granularity\n private getPeriodFormat(query: CostQuery): string {\n return query.granularity.toLowerCase() === 'daily' ? 'YYYY-MM-DD' : 'YYYY-MM';\n }\n\n private processInstanceCosts(\n costResponse: any,\n reports: Map<string, Report>,\n accountName: string,\n integrationConfig: Config,\n tagKeyValues: Record<string, string>,\n ): void {\n if (!costResponse?.instanceCosts?.instances?.length) {\n return;\n }\n\n const validInstances = costResponse.instanceCosts.instances.filter(\n (instance: { id: any; name: any }) => instance?.id && instance?.name,\n );\n\n this.logger.debug(\n `Processing ${validInstances.length} valid instances out of ${costResponse.instanceCosts.instances.length}`,\n );\n\n for (const instance of validInstances) {\n // Skip filtered instances\n if (!this.evaluateIntegrationFilters(instance.name, integrationConfig)) {\n continue;\n }\n\n const keyName = `instance-${instance.id}`;\n reports.set(keyName, {\n id: keyName,\n account: `${this.provider}/${accountName}`,\n service: this.convertServiceName(instance.name),\n category: 'Database', // TODO: find a better way in the EC API to determine the category\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n instanceId: instance.id,\n instanceName: instance.name,\n ...tagKeyValues,\n });\n }\n }\n\n private processChartData(costResponse: any, reports: Map<string, Report>, periodFormat: string): void {\n if (!costResponse?.charts?.data?.length) {\n return;\n }\n\n this.logger.info(`Processing ${costResponse.charts.data.length} time points from chart data`);\n\n for (const timePoint of costResponse.charts.data) {\n if (!timePoint?.timestamp) continue;\n\n const period = this.formatTimestamp(timePoint.timestamp, periodFormat);\n if (!period) continue;\n\n this.processTimePointValues(timePoint, period, reports);\n }\n }\n\n private processTimePointValues(timePoint: any, period: string, reports: Map<string, Report>): void {\n if (!Array.isArray(timePoint.values)) {\n return;\n }\n\n for (const value of timePoint.values) {\n if (!value?.id) continue;\n\n const instanceKey = `instance-${value.id}`;\n if (reports.has(instanceKey)) {\n reports.get(instanceKey)!.reports[period] = value.value ?? 0;\n }\n\n this.matchValueWithItemReports(value, period, reports);\n }\n }\n\n private matchValueWithItemReports(value: any, period: string, reports: Map<string, Report>): void {\n if (!value.name) return;\n\n for (const [key, report] of reports.entries()) {\n if (\n key.startsWith('item-') &&\n ((report.service && value.name.includes(report.service)) ||\n (report.productType && value.name.includes(report.productType)))\n ) {\n report.reports[period] = value.value ?? 0;\n }\n }\n }\n\n private distributeRemainingCosts(costResponse: any, reports: Map<string, Report>, query: CostQuery): void {\n const itemsData = costResponse?.itemCosts?.products;\n if (!Array.isArray(itemsData)) return;\n\n let distributedItems = 0;\n const periodFormat = this.getPeriodFormat(query);\n\n for (const product of itemsData) {\n if (!product?.type || !Array.isArray(product?.product_line_items)) continue;\n\n for (const lineItem of product.product_line_items) {\n if (!lineItem?.name) continue;\n\n const keyName = `item-${product.type}-${lineItem.name}`;\n const report = reports.get(keyName);\n\n if (report && Object.keys(report.reports).length === 0) {\n distributedItems += this.distributeItemCost(report, lineItem, query, periodFormat);\n }\n }\n }\n\n this.logger.debug(`Distributed costs across ${distributedItems} periods for items without time series data`);\n }\n\n private distributeItemCost(report: Report, lineItem: any, query: CostQuery, periodFormat: string): number {\n const totalCost = (lineItem.total_ecu ?? 0) / 100; // Convert ECU to dollars\n if (totalCost <= 0) return 0;\n\n const startMonth = moment(parseInt(query.startTime, 10));\n const endMonth = moment(parseInt(query.endTime, 10));\n const monthCount = Math.max(1, endMonth.diff(startMonth, 'months'));\n let periodsCreated = 0;\n\n for (\n let currentMonth = startMonth.clone().startOf('month');\n currentMonth.isSameOrBefore(endMonth, 'month');\n currentMonth.add(1, 'month')\n ) {\n const period = currentMonth.format(periodFormat);\n report.reports[period] = totalCost / monthCount;\n periodsCreated++;\n }\n\n return periodsCreated;\n }\n\n private prepareReportsForOutput(reports: Map<string, Report>, periodFormat: string): Report[] {\n const allReports = [...reports.values()];\n\n const filteredReports = allReports.filter(report => Object.keys(report.reports).length > 0);\n\n for (const report of filteredReports) {\n this.standardizePeriods(report, periodFormat);\n }\n\n return filteredReports;\n }\n\n private formatTimestamp(timestamp: string | number, periodFormat: string): string | null {\n try {\n if (typeof timestamp === 'number') {\n const timestampMs = timestamp < 10000000000 ? timestamp * 1000 : timestamp;\n return moment(timestampMs).format(periodFormat);\n }\n return moment(timestamp).format(periodFormat);\n } catch (error) {\n this.logger.warn(\n `Error formatting timestamp ${timestamp}: ${error instanceof Error ? error.message : String(error)}`,\n );\n return null;\n }\n }\n\n private standardizePeriods(report: Report, periodFormat: string): void {\n const formattedReports: Record<string, number> = {};\n\n for (const [period, cost] of Object.entries(report.reports)) {\n const momentDate = moment(period);\n if (!momentDate.isValid()) {\n this.logger.warn(`Invalid period format: ${period} in report ${report.id}`);\n continue;\n }\n\n const formattedPeriod = momentDate.format(periodFormat);\n formattedReports[formattedPeriod] = cost as number;\n }\n\n report.reports = formattedReports;\n }\n\n async getCostReportsFromDatabase(query: CostQuery): Promise<Report[]> {\n const reports = await super.getCostReportsFromDatabase(query);\n this.logger.debug(`Retrieved ${reports.length} ElasticCloud reports from database`);\n\n if (reports.length === 0) {\n this.logger.warn(`No ElasticCloud reports found in database for query: ${JSON.stringify(query)}`);\n }\n\n return reports;\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","moment","GetChartsRequestSchema","ZodError","InstancesResponseSchema","ItemsResponseSchema","ChartsResponseSchema","PROVIDER_TYPE"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ElasticCloudClient.cjs.js","sources":["../../src/cost-clients/ElasticCloudClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport moment from 'moment';\nimport {\n GetChartsRequestSchema,\n ChartsResponseSchema,\n InstancesResponseSchema,\n ItemsResponseSchema,\n} from '../schemas/ElasticBilling';\nimport { ZodError } from 'zod';\nimport { CLOUD_PROVIDER, PROVIDER_TYPE } from '../service/consts';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\n\n/**\n * Client for fetching and processing cost data from Elastic Cloud\n */\nexport class ElasticCloudClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new ElasticCloudClient(CLOUD_PROVIDER.ELASTIC_CLOUD, config, database, cache, logger);\n }\n\n protected convertServiceName(serviceName: string): string {\n return serviceName.startsWith('Elastic')\n ? `${this.provider}/${serviceName.slice('Elastic'.length).trim()}`\n : `${this.provider}/${serviceName}`;\n }\n\n protected async initCloudClient(integrationConfig: Config): Promise<any> {\n const apiKey = integrationConfig.getString('apiKey');\n\n return {\n baseUrl: 'https://billing.elastic-cloud.com',\n headers: {\n Authorization: `ApiKey ${apiKey}`,\n 'Content-Type': 'application/json',\n },\n };\n }\n\n private async fetchWithRetry(url: string, headers: any, maxRetries = 3): Promise<any> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(url, { method: 'GET', headers });\n\n if (response.status === 429 && attempt < maxRetries) {\n const retryAfter = parseInt(response.headers.get('retry-after') ?? '5', 10);\n this.logger.warn(`Rate limited by Elastic Cloud API, retrying after ${retryAfter} seconds...`);\n await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));\n continue;\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Elastic Cloud API error (${response.status}): ${errorText}`);\n }\n\n return await response.json();\n } catch (error) {\n if (attempt < maxRetries) {\n const backoffTime = Math.pow(2, attempt) * 1000;\n this.logger.warn(`Error fetching from Elastic Cloud, retrying in ${backoffTime}ms: ${error.message}`);\n await new Promise(resolve => setTimeout(resolve, backoffTime));\n } else {\n throw error;\n }\n }\n }\n throw new Error(`Failed to fetch from Elastic Cloud API after ${maxRetries} attempts`);\n }\n\n protected async fetchCosts(integrationConfig: Config, client: any, query: CostQuery): Promise<any> {\n const { baseUrl, headers } = client;\n const organizationId = integrationConfig.getString('organizationId');\n // Convert to ISO 8601 format that's compliant with Zod's datetime validation\n // Use the Z suffix instead of +00:00 which may not be recognized by the schema\n const startDate = moment(parseInt(query.startTime, 10)).toISOString();\n const endDate = moment(parseInt(query.endTime, 10)).toISOString();\n const bucketingStrategy = query.granularity.toLowerCase() === 'daily' ? 'daily' : 'monthly';\n\n this.logger.info(\n `Fetching Elastic Cloud cost data from ${startDate} to ${endDate} with ${bucketingStrategy} granularity`,\n );\n\n try {\n const params = {\n from: startDate,\n to: endDate,\n bucketing_strategy: bucketingStrategy,\n };\n\n // The Elastic Cloud API requires ISO format dates, but the zod schemas expect a specific format\n // Instead of modifying the schemas, we'll use the params directly, logging validation issues\n try {\n GetChartsRequestSchema.parse(params);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(\n `Request parameters didn't match schema for GetChartsRequest: ${JSON.stringify(error.errors)}`,\n );\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n this.logger.debug(`Using params directly: ${JSON.stringify(params)}`);\n }\n\n const chartParams = params;\n const instanceParams = { ...params, include_names: true };\n const itemsParams = { from: params.from, to: params.to };\n\n const createQueryString = (queryParams: Record<string, any>) => {\n return Object.entries(queryParams)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)\n .join('&');\n };\n\n const [instanceCostsResponse, itemCostsResponse, chartsResponse] = await Promise.all([\n this.fetchWithRetry(\n `${baseUrl}/api/v2/billing/organizations/${organizationId}/costs/instances?${createQueryString(instanceParams)}`,\n headers,\n ).then(data => {\n const validationResult = InstancesResponseSchema.safeParse(data);\n if (!validationResult.success) {\n this.logger.warn(`Response validation failed for instance costs: ${validationResult.error.message}`);\n } else {\n this.logger.debug(`Response validation passed for instance costs`);\n }\n this.logger.debug(`Received instance costs data with ${data?.instances?.length ?? 0} instances`);\n return data;\n }),\n\n this.fetchWithRetry(\n `${baseUrl}/api/v2/billing/organizations/${organizationId}/costs/items?${createQueryString(itemsParams)}`,\n headers,\n ).then(data => {\n const validationResult = ItemsResponseSchema.safeParse(data);\n if (!validationResult.success) {\n this.logger.warn(`Response validation failed for item costs: ${validationResult.error.message}`);\n } else {\n this.logger.debug(`Response validation passed for item costs`);\n }\n this.logger.debug(`Received item costs data with ${data?.products?.length ?? 0} products`);\n return data;\n }),\n\n this.fetchWithRetry(\n `${baseUrl}/api/v2/billing/organizations/${organizationId}/charts?${createQueryString(chartParams)}`,\n headers,\n ).then(data => {\n const validationResult = ChartsResponseSchema.safeParse(data);\n if (!validationResult.success) {\n this.logger.warn(`Response validation failed for charts data: ${validationResult.error.message}`);\n } else {\n this.logger.debug(`Response validation passed for charts data`);\n }\n this.logger.debug(`Received charts data with ${data?.data?.length ?? 0} data points`);\n return data;\n }),\n ]);\n\n return {\n instanceCosts: instanceCostsResponse,\n itemCosts: itemCostsResponse,\n charts: chartsResponse,\n };\n } catch (error) {\n this.logger.error(`Error fetching Elastic Cloud costs: ${error.message}`);\n throw error;\n }\n }\n\n protected async transformCostsData(\n integrationConfig: Config,\n query: CostQuery,\n costResponse: any,\n ): Promise<Report[]> {\n const accountName = integrationConfig.getString('name');\n const tagKeyValues = this.extractConfigTags(integrationConfig);\n\n // Initialize tracking variables - calculate totalRecords from different parts of costResponse\n let totalRecords = 0;\n\n if (costResponse?.instanceCosts?.instances?.length) {\n totalRecords += costResponse.instanceCosts.instances.length;\n }\n if (costResponse?.itemCosts?.products?.length) {\n totalRecords += costResponse.itemCosts.products.length;\n }\n if (costResponse?.charts?.data?.length) {\n totalRecords += costResponse.charts.data.length;\n }\n\n try {\n const reports = new Map();\n const periodFormat = this.getPeriodFormat(query);\n const uniqueKeys = new Set<string>();\n\n // Track metrics during processing\n const metrics = {\n processed: 0,\n zeroAmount: 0,\n missingFields: 0,\n invalidDate: 0,\n timeRange: 0,\n };\n\n this.processInstanceCosts(costResponse, reports, accountName, integrationConfig, tagKeyValues);\n this.processChartData(costResponse, reports, periodFormat);\n this.distributeRemainingCosts(costResponse, reports, query);\n\n const filteredReports = this.prepareReportsForOutput(reports, periodFormat);\n\n // Estimate processed records based on successful reports\n metrics.processed = filteredReports.reduce((sum, report) => sum + Object.keys(report.reports).length, 0);\n\n // Count unique keys from final reports\n filteredReports.forEach(report => uniqueKeys.add(report.id));\n\n this.logTransformationSummary({\n processed: metrics.processed,\n uniqueReports: uniqueKeys.size,\n zeroAmount: metrics.zeroAmount,\n missingFields: metrics.missingFields,\n invalidDate: metrics.invalidDate,\n timeRange: metrics.timeRange,\n totalRecords,\n });\n\n return filteredReports;\n } catch (error) {\n this.logger.error(`Error transforming Elastic Cloud cost data: ${error.message}`);\n throw error;\n }\n }\n\n private extractConfigTags(integrationConfig: Config): Record<string, string> {\n const tags = integrationConfig.getOptionalStringArray('tags') ?? [];\n const tagKeyValues: Record<string, string> = {};\n\n tags.forEach(tag => {\n const [k, v] = tag.split(':').map(part => part.trim());\n tagKeyValues[k] = v;\n });\n\n return tagKeyValues;\n }\n\n // Determine period format based on granularity\n private getPeriodFormat(query: CostQuery): string {\n return query.granularity.toLowerCase() === 'daily' ? 'YYYY-MM-DD' : 'YYYY-MM';\n }\n\n private processInstanceCosts(\n costResponse: any,\n reports: Map<string, Report>,\n accountName: string,\n integrationConfig: Config,\n tagKeyValues: Record<string, string>,\n ): void {\n if (!costResponse?.instanceCosts?.instances?.length) {\n return;\n }\n\n const validInstances = costResponse.instanceCosts.instances.filter(\n (instance: { id: any; name: any }) => instance?.id && instance?.name,\n );\n\n this.logger.debug(\n `Processing ${validInstances.length} valid instances out of ${costResponse.instanceCosts.instances.length}`,\n );\n\n for (const instance of validInstances) {\n // Skip filtered instances\n if (!this.evaluateIntegrationFilters(instance.name, integrationConfig)) {\n continue;\n }\n\n const keyName = `instance-${instance.id}`;\n reports.set(keyName, {\n id: keyName,\n account: `${this.provider}/${accountName}`,\n service: this.convertServiceName(instance.name),\n category: 'Database', // TODO: find a better way in the EC API to determine the category\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n instanceId: instance.id,\n instanceName: instance.name,\n ...tagKeyValues,\n });\n }\n }\n\n private processChartData(costResponse: any, reports: Map<string, Report>, periodFormat: string): void {\n if (!costResponse?.charts?.data?.length) {\n return;\n }\n\n this.logger.info(`Processing ${costResponse.charts.data.length} time points from chart data`);\n\n for (const timePoint of costResponse.charts.data) {\n if (!timePoint?.timestamp) continue;\n\n const period = this.formatTimestamp(timePoint.timestamp, periodFormat);\n if (!period) continue;\n\n this.processTimePointValues(timePoint, period, reports);\n }\n }\n\n private processTimePointValues(timePoint: any, period: string, reports: Map<string, Report>): void {\n if (!Array.isArray(timePoint.values)) {\n return;\n }\n\n for (const value of timePoint.values) {\n // Skip if value is null/undefined or missing required fields\n if (!value?.id) continue;\n\n const instanceKey = `instance-${value.id}`;\n if (reports.has(instanceKey)) {\n const rawValue = value.value ?? 0;\n this.logger.debug(`Chart value for ${instanceKey} period ${period}: raw=${rawValue}`);\n reports.get(instanceKey)!.reports[period] = rawValue; // Using raw value for now\n }\n\n this.matchValueWithItemReports(value, period, reports);\n }\n }\n\n private matchValueWithItemReports(value: any, period: string, reports: Map<string, Report>): void {\n if (!value?.name) return;\n\n for (const [key, report] of reports.entries()) {\n if (\n key.startsWith('item-') &&\n ((report.service && value.name.includes(report.service)) ||\n (report.productType && value.name.includes(report.productType)))\n ) {\n const rawValue = value.value ?? 0;\n this.logger.debug(`Item value for ${key} period ${period}: raw=${rawValue}`);\n report.reports[period] = rawValue; // Using raw value for now\n }\n }\n }\n\n private distributeRemainingCosts(costResponse: any, reports: Map<string, Report>, query: CostQuery): void {\n const itemsData = costResponse?.itemCosts?.products;\n if (!Array.isArray(itemsData)) return;\n\n let distributedItems = 0;\n const periodFormat = this.getPeriodFormat(query);\n\n for (const product of itemsData) {\n if (!product?.type || !Array.isArray(product?.product_line_items)) continue;\n\n for (const lineItem of product.product_line_items) {\n if (!lineItem?.name) continue;\n\n const keyName = `item-${product.type}-${lineItem.name}`;\n const report = reports.get(keyName);\n\n if (report && Object.keys(report.reports).length === 0) {\n distributedItems += this.distributeItemCost(report, lineItem, query, periodFormat);\n }\n }\n }\n\n this.logger.debug(`Distributed costs across ${distributedItems} periods for items without time series data`);\n }\n\n private distributeItemCost(report: Report, lineItem: any, query: CostQuery, periodFormat: string): number {\n const rawTotalEcu = lineItem.total_ecu ?? 0;\n const totalCost = rawTotalEcu / 100; // Keep original conversion for line items\n this.logger.debug(`Distribute item cost for ${report.id}: raw_ecu=${rawTotalEcu}, converted_dollars=${totalCost}`);\n if (totalCost <= 0) return 0;\n\n const startMonth = moment(parseInt(query.startTime, 10));\n const endMonth = moment(parseInt(query.endTime, 10));\n const monthCount = Math.max(1, endMonth.diff(startMonth, 'months'));\n let periodsCreated = 0;\n\n for (\n let currentMonth = startMonth.clone().startOf('month');\n currentMonth.isSameOrBefore(endMonth, 'month');\n currentMonth.add(1, 'month')\n ) {\n const period = currentMonth.format(periodFormat);\n report.reports[period] = totalCost / monthCount;\n periodsCreated++;\n }\n\n return periodsCreated;\n }\n\n private prepareReportsForOutput(reports: Map<string, Report>, periodFormat: string): Report[] {\n const allReports = [...reports.values()];\n\n const filteredReports = allReports.filter(report => Object.keys(report.reports).length > 0);\n\n for (const report of filteredReports) {\n this.standardizePeriods(report, periodFormat);\n }\n\n return filteredReports;\n }\n\n private formatTimestamp(timestamp: string | number, periodFormat: string): string | null {\n try {\n if (typeof timestamp === 'number') {\n const timestampMs = timestamp < 10000000000 ? timestamp * 1000 : timestamp;\n return moment(timestampMs).format(periodFormat);\n }\n return moment(timestamp).format(periodFormat);\n } catch (error) {\n this.logger.warn(\n `Error formatting timestamp ${timestamp}: ${error instanceof Error ? error.message : String(error)}`,\n );\n return null;\n }\n }\n\n private standardizePeriods(report: Report, periodFormat: string): void {\n const formattedReports: Record<string, number> = {};\n\n for (const [period, cost] of Object.entries(report.reports)) {\n const momentDate = moment(period);\n if (!momentDate.isValid()) {\n this.logger.warn(`Invalid period format: ${period} in report ${report.id}`);\n continue;\n }\n\n const formattedPeriod = momentDate.format(periodFormat);\n formattedReports[formattedPeriod] = cost as number;\n }\n\n report.reports = formattedReports;\n }\n\n async getCostReportsFromDatabase(query: CostQuery): Promise<Report[]> {\n const reports = await super.getCostReportsFromDatabase(query);\n this.logger.debug(`Retrieved ${reports.length} ElasticCloud reports from database`);\n\n if (reports.length === 0) {\n this.logger.warn(`No ElasticCloud reports found in database for query: ${JSON.stringify(query)}`);\n }\n\n return reports;\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","moment","GetChartsRequestSchema","ZodError","InstancesResponseSchema","ItemsResponseSchema","ChartsResponseSchema","PROVIDER_TYPE"],"mappings":"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"}
|
|
@@ -102,7 +102,7 @@ class GCPClient extends InfraWalletClient.InfraWalletClient {
|
|
|
102
102
|
const [job] = await client.createQueryJob(queryOptions);
|
|
103
103
|
const [rows] = await job.getQueryResults();
|
|
104
104
|
try {
|
|
105
|
-
GCPBilling.
|
|
105
|
+
GCPBilling.GCPCustomQueryResultSchema.parse(rows);
|
|
106
106
|
this.logger.debug(`GCP billing data validation passed for ${rows.length} records`);
|
|
107
107
|
} catch (error) {
|
|
108
108
|
if (error instanceof zod.ZodError) {
|
|
@@ -181,12 +181,29 @@ class GCPClient extends InfraWalletClient.InfraWalletClient {
|
|
|
181
181
|
const [k, v] = tag.split(":");
|
|
182
182
|
tagKeyValues[k.trim()] = v.trim();
|
|
183
183
|
});
|
|
184
|
+
let processedRecords = 0;
|
|
185
|
+
let filteredOutZeroAmount = 0;
|
|
186
|
+
let filteredOutMissingFields = 0;
|
|
187
|
+
const filteredOutInvalidDate = 0;
|
|
188
|
+
const filteredOutTimeRange = 0;
|
|
189
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
190
|
+
const totalRecords = costResponse?.length || 0;
|
|
184
191
|
const transformedData = lodash.reduce(
|
|
185
192
|
costResponse,
|
|
186
193
|
(acc, row) => {
|
|
194
|
+
if (!row.period || !row.project || !row.service || row.total_cost === void 0 || row.total_cost === null) {
|
|
195
|
+
filteredOutMissingFields++;
|
|
196
|
+
return acc;
|
|
197
|
+
}
|
|
198
|
+
const amount = functions.parseCost(row.total_cost);
|
|
199
|
+
if (amount === 0) {
|
|
200
|
+
filteredOutZeroAmount++;
|
|
201
|
+
return acc;
|
|
202
|
+
}
|
|
187
203
|
const period = row.period;
|
|
188
204
|
const keyName = `${accountName}_${row.project}_${row.service}`;
|
|
189
205
|
if (!acc[keyName]) {
|
|
206
|
+
uniqueKeys.add(keyName);
|
|
190
207
|
acc[keyName] = {
|
|
191
208
|
id: keyName,
|
|
192
209
|
account: `${this.provider}/${accountName}`,
|
|
@@ -201,11 +218,21 @@ class GCPClient extends InfraWalletClient.InfraWalletClient {
|
|
|
201
218
|
// note that if there is a tag `project:foo` in config, it overrides the project field set above
|
|
202
219
|
};
|
|
203
220
|
}
|
|
204
|
-
acc[keyName].reports[period] =
|
|
221
|
+
acc[keyName].reports[period] = amount;
|
|
222
|
+
processedRecords++;
|
|
205
223
|
return acc;
|
|
206
224
|
},
|
|
207
225
|
{}
|
|
208
226
|
);
|
|
227
|
+
this.logTransformationSummary({
|
|
228
|
+
processed: processedRecords,
|
|
229
|
+
uniqueReports: uniqueKeys.size,
|
|
230
|
+
zeroAmount: filteredOutZeroAmount,
|
|
231
|
+
missingFields: filteredOutMissingFields,
|
|
232
|
+
invalidDate: filteredOutInvalidDate,
|
|
233
|
+
timeRange: filteredOutTimeRange,
|
|
234
|
+
totalRecords
|
|
235
|
+
});
|
|
209
236
|
return Object.values(transformedData);
|
|
210
237
|
}
|
|
211
238
|
}
|