@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.
Files changed (38) hide show
  1. package/dist/cost-clients/AwsClient.cjs.js +34 -7
  2. package/dist/cost-clients/AwsClient.cjs.js.map +1 -1
  3. package/dist/cost-clients/AzureClient.cjs.js +31 -2
  4. package/dist/cost-clients/AzureClient.cjs.js.map +1 -1
  5. package/dist/cost-clients/ConfluentClient.cjs.js +50 -2
  6. package/dist/cost-clients/ConfluentClient.cjs.js.map +1 -1
  7. package/dist/cost-clients/CustomProviderClient.cjs.js +30 -1
  8. package/dist/cost-clients/CustomProviderClient.cjs.js.map +1 -1
  9. package/dist/cost-clients/DatadogClient.cjs.js +38 -2
  10. package/dist/cost-clients/DatadogClient.cjs.js.map +1 -1
  11. package/dist/cost-clients/ElasticCloudClient.cjs.js +39 -7
  12. package/dist/cost-clients/ElasticCloudClient.cjs.js.map +1 -1
  13. package/dist/cost-clients/GCPClient.cjs.js +29 -2
  14. package/dist/cost-clients/GCPClient.cjs.js.map +1 -1
  15. package/dist/cost-clients/GitHubClient.cjs.js +28 -2
  16. package/dist/cost-clients/GitHubClient.cjs.js.map +1 -1
  17. package/dist/cost-clients/InfraWalletClient.cjs.js +3 -0
  18. package/dist/cost-clients/InfraWalletClient.cjs.js.map +1 -1
  19. package/dist/cost-clients/MockClient.cjs.js +18 -0
  20. package/dist/cost-clients/MockClient.cjs.js.map +1 -1
  21. package/dist/cost-clients/MongoAtlasClient.cjs.js +56 -24
  22. package/dist/cost-clients/MongoAtlasClient.cjs.js.map +1 -1
  23. package/dist/schemas/AzureBilling.cjs.js +15 -7
  24. package/dist/schemas/AzureBilling.cjs.js.map +1 -1
  25. package/dist/schemas/ConfluentBilling.cjs.js +20 -8
  26. package/dist/schemas/ConfluentBilling.cjs.js.map +1 -1
  27. package/dist/schemas/DatadogBilling.cjs.js +14 -5
  28. package/dist/schemas/DatadogBilling.cjs.js.map +1 -1
  29. package/dist/schemas/ElasticBilling.cjs.js +5 -5
  30. package/dist/schemas/ElasticBilling.cjs.js.map +1 -1
  31. package/dist/schemas/GCPBilling.cjs.js +48 -18
  32. package/dist/schemas/GCPBilling.cjs.js.map +1 -1
  33. package/dist/service/functions.cjs.js +6 -0
  34. package/dist/service/functions.cjs.js.map +1 -1
  35. package/dist/tasks/fetchAndSaveCosts.cjs.js +1 -1
  36. package/dist/tasks/fetchAndSaveCosts.cjs.js.map +1 -1
  37. package/mock/mock_response.json +252 -0
  38. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"GCPClient.cjs.js","sources":["../../src/cost-clients/GCPClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { BigQuery } from '@google-cloud/bigquery';\nimport { existsSync } from 'fs';\nimport { reduce } from 'lodash';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { CategoryMappingService } from '../service/CategoryMappingService';\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 { GCPBillingQueryResultSchema } from '../schemas/GCPBilling';\nimport { ZodError } from 'zod';\n\nexport class GCPClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new GCPClient(CLOUD_PROVIDER.GCP, config, database, cache, logger);\n }\n\n protected convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['Google Cloud'];\n\n for (const prefix of prefixes) {\n if (serviceName.startsWith(prefix)) {\n convertedName = serviceName.slice(prefix.length).trim();\n }\n }\n\n return `${this.provider}/${convertedName}`;\n }\n\n /**\n * Resolves a file path, handling POSIX-style paths with environment variables and tilde expansion.\n * Supports:\n * - Relative paths: \"./path/to/file.json\"\n * - Absolute paths: \"/path/to/file.json\"\n * - Home directory: \"~/path/to/file.json\"\n * - Environment variables: \"$HOME/path/to/file.json\", \"${HOME}/path/to/file.json\"\n *\n * @param filePath The path to resolve\n * @returns The resolved path, or null if the path cannot be resolved\n */\n private resolvePath(filePath: string): string | null {\n try {\n let resolvedPath = filePath.replace(/\\$([A-Za-z0-9_]+)|\\$\\{([A-Za-z0-9_]+)\\}/g, (match, p1, p2) => {\n const varName = p1 || p2;\n const value = process.env[varName];\n if (!value) {\n this.logger.warn(`Environment variable ${varName} not found when resolving path: ${filePath}`);\n return match;\n }\n return value;\n });\n\n if (resolvedPath.startsWith('~')) {\n const homeDirectory = homedir();\n resolvedPath = join(homeDirectory, resolvedPath.substring(1));\n }\n\n if (!existsSync(resolvedPath)) {\n this.logger.warn(`File does not exist at resolved path: ${resolvedPath}`);\n return null;\n }\n\n return resolvedPath;\n } catch (error) {\n this.logger.error(`Error resolving path ${filePath}: ${error.message}`);\n return null;\n }\n }\n\n protected async initCloudClient(subAccountConfig: Config): Promise<any> {\n const projectId = subAccountConfig.getString('projectId');\n const options: any = { projectId };\n\n const adcEnvPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;\n if (adcEnvPath) {\n if (existsSync(adcEnvPath)) {\n this.logger.info(`Using GCP credentials from GOOGLE_APPLICATION_CREDENTIALS env var: ${adcEnvPath}`);\n } else {\n this.logger.warn(`GOOGLE_APPLICATION_CREDENTIALS points to non-existent file: ${adcEnvPath}`);\n }\n }\n\n const keyFilePath = subAccountConfig.getOptionalString('keyFilePath');\n\n if (keyFilePath) {\n const resolvedPath = this.resolvePath(keyFilePath);\n\n if (resolvedPath) {\n this.logger.info(`Using GCP credentials file at: ${resolvedPath}`);\n options.keyFilename = resolvedPath;\n\n if (adcEnvPath) {\n this.logger.info('Overriding GOOGLE_APPLICATION_CREDENTIALS with keyFilePath from config');\n }\n } else {\n this.logger.info(\n `GCP credentials file not found at ${keyFilePath}, falling back to application default credentials`,\n );\n }\n } else if (!adcEnvPath) {\n this.logger.info(\n 'No keyFilePath or GOOGLE_APPLICATION_CREDENTIALS specified, using application default credentials',\n );\n }\n\n const bigqueryClient = new BigQuery(options);\n return bigqueryClient;\n }\n\n private async fetchDataWithRetry(client: any, queryOptions: any, maxRetries = 5): Promise<any> {\n let retries = 0;\n\n while (retries < maxRetries) {\n try {\n const [job] = await client.createQueryJob(queryOptions);\n const [rows] = await job.getQueryResults();\n\n try {\n GCPBillingQueryResultSchema.parse(rows);\n this.logger.debug(`GCP billing data validation passed for ${rows.length} records`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`GCP billing data 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 return rows;\n } catch (err) {\n const errorMessage = err.message || '';\n const errorCode = err.code || '';\n\n // Check for rate limiting and quota errors\n if (errorCode === 429 || errorMessage.includes('quotaExceeded') || errorMessage.includes('rateLimitExceeded')) {\n const retryDelay = Math.min(60 * Math.pow(2, retries), 300); // Exponential backoff, max 5 minutes\n this.logger.warn(\n `Hit BigQuery rate limit/quota, retrying after ${retryDelay} seconds... (attempt ${retries + 1}/${maxRetries})`,\n );\n await new Promise(resolve => setTimeout(resolve, retryDelay * 1000));\n retries++;\n continue;\n }\n\n // Check for transient backend errors\n if (errorMessage.includes('backendError') || errorMessage.includes('internalError') || errorCode >= 500) {\n const retryDelay = Math.min(30 * Math.pow(2, retries), 120); // Shorter backoff for backend errors\n this.logger.warn(\n `BigQuery backend error, retrying after ${retryDelay} seconds... (attempt ${retries + 1}/${maxRetries}): ${errorMessage}`,\n );\n await new Promise(resolve => setTimeout(resolve, retryDelay * 1000));\n retries++;\n continue;\n }\n\n // For non-retryable errors, throw immediately\n this.logger.error(`Non-retryable BigQuery error: ${errorMessage}`);\n throw err;\n }\n }\n\n throw new Error(`Max retries (${maxRetries}) exceeded for BigQuery operation`);\n }\n\n protected async fetchCosts(subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\n const projectId = subAccountConfig.getString('projectId');\n const datasetId = subAccountConfig.getString('datasetId');\n const tableId = subAccountConfig.getString('tableId');\n\n try {\n const periodFormat = query.granularity === GRANULARITY.MONTHLY ? '%Y-%m' : '%Y-%m-%d';\n const sql = `\n SELECT\n project.name AS project,\n service.description AS service,\n FORMAT_TIMESTAMP('${periodFormat}', usage_start_time) AS period,\n (SUM(CAST(cost AS NUMERIC)) + SUM(IFNULL((SELECT SUM(CAST(c.amount AS NUMERIC)) FROM UNNEST(credits) AS c), 0))) AS total_cost\n FROM\n \\`${projectId}.${datasetId}.${tableId}\\`\n WHERE\n project.name IS NOT NULL\n AND cost > 0\n AND usage_start_time >= TIMESTAMP_MILLIS(${query.startTime})\n AND usage_start_time <= TIMESTAMP_MILLIS(${query.endTime})\n GROUP BY\n project, service, period\n ORDER BY\n project, period, total_cost DESC`;\n\n const queryOptions = {\n query: sql,\n };\n\n return await this.fetchDataWithRetry(client, queryOptions);\n } catch (err) {\n this.logger.error(`Error executing BigQuery after retries: ${err.message}`);\n throw new Error(err.message);\n }\n }\n\n protected async transformCostsData(\n subAccountConfig: Config,\n _query: CostQuery,\n costResponse: any,\n ): Promise<Report[]> {\n const categoryMappingService = CategoryMappingService.getInstance();\n const accountName = subAccountConfig.getString('name');\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 const transformedData = reduce(\n costResponse,\n (acc: { [key: string]: Report }, row) => {\n const period = row.period;\n const keyName = `${accountName}_${row.project}_${row.service}`;\n\n if (!acc[keyName]) {\n acc[keyName] = {\n id: keyName,\n account: `${this.provider}/${accountName}`,\n service: this.convertServiceName(row.service),\n category: categoryMappingService.getCategoryByServiceName(this.provider, row.service),\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...{ project: row.project }, // TODO: how should we handle the project field? for now, we add project name as a field in the report\n ...tagKeyValues, // note that if there is a tag `project:foo` in config, it overrides the project field set above\n };\n }\n\n acc[keyName].reports[period] = parseCost(row.total_cost);\n return acc;\n },\n {},\n );\n\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","homedir","join","existsSync","BigQuery","GCPBillingQueryResultSchema","ZodError","GRANULARITY","CategoryMappingService","reduce","PROVIDER_TYPE","parseCost"],"mappings":"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"}
1
+ {"version":3,"file":"GCPClient.cjs.js","sources":["../../src/cost-clients/GCPClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { BigQuery } from '@google-cloud/bigquery';\nimport { existsSync } from 'fs';\nimport { reduce } from 'lodash';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { CategoryMappingService } from '../service/CategoryMappingService';\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 { GCPCustomQueryResultSchema } from '../schemas/GCPBilling';\nimport { ZodError } from 'zod';\n\nexport class GCPClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new GCPClient(CLOUD_PROVIDER.GCP, config, database, cache, logger);\n }\n\n protected convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['Google Cloud'];\n\n for (const prefix of prefixes) {\n if (serviceName.startsWith(prefix)) {\n convertedName = serviceName.slice(prefix.length).trim();\n }\n }\n\n return `${this.provider}/${convertedName}`;\n }\n\n /**\n * Resolves a file path, handling POSIX-style paths with environment variables and tilde expansion.\n * Supports:\n * - Relative paths: \"./path/to/file.json\"\n * - Absolute paths: \"/path/to/file.json\"\n * - Home directory: \"~/path/to/file.json\"\n * - Environment variables: \"$HOME/path/to/file.json\", \"${HOME}/path/to/file.json\"\n *\n * @param filePath The path to resolve\n * @returns The resolved path, or null if the path cannot be resolved\n */\n private resolvePath(filePath: string): string | null {\n try {\n let resolvedPath = filePath.replace(/\\$([A-Za-z0-9_]+)|\\$\\{([A-Za-z0-9_]+)\\}/g, (match, p1, p2) => {\n const varName = p1 || p2;\n const value = process.env[varName];\n if (!value) {\n this.logger.warn(`Environment variable ${varName} not found when resolving path: ${filePath}`);\n return match;\n }\n return value;\n });\n\n if (resolvedPath.startsWith('~')) {\n const homeDirectory = homedir();\n resolvedPath = join(homeDirectory, resolvedPath.substring(1));\n }\n\n if (!existsSync(resolvedPath)) {\n this.logger.warn(`File does not exist at resolved path: ${resolvedPath}`);\n return null;\n }\n\n return resolvedPath;\n } catch (error) {\n this.logger.error(`Error resolving path ${filePath}: ${error.message}`);\n return null;\n }\n }\n\n protected async initCloudClient(subAccountConfig: Config): Promise<any> {\n const projectId = subAccountConfig.getString('projectId');\n const options: any = { projectId };\n\n const adcEnvPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;\n if (adcEnvPath) {\n if (existsSync(adcEnvPath)) {\n this.logger.info(`Using GCP credentials from GOOGLE_APPLICATION_CREDENTIALS env var: ${adcEnvPath}`);\n } else {\n this.logger.warn(`GOOGLE_APPLICATION_CREDENTIALS points to non-existent file: ${adcEnvPath}`);\n }\n }\n\n const keyFilePath = subAccountConfig.getOptionalString('keyFilePath');\n\n if (keyFilePath) {\n const resolvedPath = this.resolvePath(keyFilePath);\n\n if (resolvedPath) {\n this.logger.info(`Using GCP credentials file at: ${resolvedPath}`);\n options.keyFilename = resolvedPath;\n\n if (adcEnvPath) {\n this.logger.info('Overriding GOOGLE_APPLICATION_CREDENTIALS with keyFilePath from config');\n }\n } else {\n this.logger.info(\n `GCP credentials file not found at ${keyFilePath}, falling back to application default credentials`,\n );\n }\n } else if (!adcEnvPath) {\n this.logger.info(\n 'No keyFilePath or GOOGLE_APPLICATION_CREDENTIALS specified, using application default credentials',\n );\n }\n\n const bigqueryClient = new BigQuery(options);\n return bigqueryClient;\n }\n\n private async fetchDataWithRetry(client: any, queryOptions: any, maxRetries = 5): Promise<any> {\n let retries = 0;\n\n while (retries < maxRetries) {\n try {\n const [job] = await client.createQueryJob(queryOptions);\n const [rows] = await job.getQueryResults();\n\n try {\n GCPCustomQueryResultSchema.parse(rows);\n this.logger.debug(`GCP billing data validation passed for ${rows.length} records`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`GCP billing data 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 return rows;\n } catch (err) {\n const errorMessage = err.message || '';\n const errorCode = err.code || '';\n\n // Check for rate limiting and quota errors\n if (errorCode === 429 || errorMessage.includes('quotaExceeded') || errorMessage.includes('rateLimitExceeded')) {\n const retryDelay = Math.min(60 * Math.pow(2, retries), 300); // Exponential backoff, max 5 minutes\n this.logger.warn(\n `Hit BigQuery rate limit/quota, retrying after ${retryDelay} seconds... (attempt ${retries + 1}/${maxRetries})`,\n );\n await new Promise(resolve => setTimeout(resolve, retryDelay * 1000));\n retries++;\n continue;\n }\n\n // Check for transient backend errors\n if (errorMessage.includes('backendError') || errorMessage.includes('internalError') || errorCode >= 500) {\n const retryDelay = Math.min(30 * Math.pow(2, retries), 120); // Shorter backoff for backend errors\n this.logger.warn(\n `BigQuery backend error, retrying after ${retryDelay} seconds... (attempt ${retries + 1}/${maxRetries}): ${errorMessage}`,\n );\n await new Promise(resolve => setTimeout(resolve, retryDelay * 1000));\n retries++;\n continue;\n }\n\n // For non-retryable errors, throw immediately\n this.logger.error(`Non-retryable BigQuery error: ${errorMessage}`);\n throw err;\n }\n }\n\n throw new Error(`Max retries (${maxRetries}) exceeded for BigQuery operation`);\n }\n\n protected async fetchCosts(subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\n const projectId = subAccountConfig.getString('projectId');\n const datasetId = subAccountConfig.getString('datasetId');\n const tableId = subAccountConfig.getString('tableId');\n\n try {\n const periodFormat = query.granularity === GRANULARITY.MONTHLY ? '%Y-%m' : '%Y-%m-%d';\n const sql = `\n SELECT\n project.name AS project,\n service.description AS service,\n FORMAT_TIMESTAMP('${periodFormat}', usage_start_time) AS period,\n (SUM(CAST(cost AS NUMERIC)) + SUM(IFNULL((SELECT SUM(CAST(c.amount AS NUMERIC)) FROM UNNEST(credits) AS c), 0))) AS total_cost\n FROM\n \\`${projectId}.${datasetId}.${tableId}\\`\n WHERE\n project.name IS NOT NULL\n AND cost > 0\n AND usage_start_time >= TIMESTAMP_MILLIS(${query.startTime})\n AND usage_start_time <= TIMESTAMP_MILLIS(${query.endTime})\n GROUP BY\n project, service, period\n ORDER BY\n project, period, total_cost DESC`;\n\n const queryOptions = {\n query: sql,\n };\n\n return await this.fetchDataWithRetry(client, queryOptions);\n } catch (err) {\n this.logger.error(`Error executing BigQuery after retries: ${err.message}`);\n throw new Error(err.message);\n }\n }\n\n protected async transformCostsData(\n subAccountConfig: Config,\n _query: CostQuery,\n costResponse: any,\n ): Promise<Report[]> {\n const categoryMappingService = CategoryMappingService.getInstance();\n const accountName = subAccountConfig.getString('name');\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 const 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 (acc: { [key: string]: Report }, row) => {\n // Check for missing fields\n if (!row.period || !row.project || !row.service || row.total_cost === undefined || row.total_cost === null) {\n filteredOutMissingFields++;\n return acc;\n }\n\n const amount = parseCost(row.total_cost);\n\n // Check for zero amount\n if (amount === 0) {\n filteredOutZeroAmount++;\n return acc;\n }\n\n const period = row.period;\n const keyName = `${accountName}_${row.project}_${row.service}`;\n\n if (!acc[keyName]) {\n uniqueKeys.add(keyName);\n acc[keyName] = {\n id: keyName,\n account: `${this.provider}/${accountName}`,\n service: this.convertServiceName(row.service),\n category: categoryMappingService.getCategoryByServiceName(this.provider, row.service),\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...{ project: row.project }, // TODO: how should we handle the project field? for now, we add project name as a field in the report\n ...tagKeyValues, // note that if there is a tag `project:foo` in config, it overrides the project field set above\n };\n }\n\n acc[keyName].reports[period] = amount;\n processedRecords++;\n return acc;\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","homedir","join","existsSync","BigQuery","GCPCustomQueryResultSchema","ZodError","GRANULARITY","CategoryMappingService","reduce","parseCost","PROVIDER_TYPE"],"mappings":"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"}
@@ -53,15 +53,31 @@ class GitHubClient extends InfraWalletClient.InfraWalletClient {
53
53
  return allUsageItems;
54
54
  }
55
55
  async transformCostsData(_integrationConfig, query, costResponse) {
56
+ let processedRecords = 0;
57
+ let filteredOutZeroAmount = 0;
58
+ let filteredOutMissingFields = 0;
59
+ const filteredOutInvalidDate = 0;
60
+ const filteredOutTimeRange = 0;
61
+ const uniqueKeys = /* @__PURE__ */ new Set();
62
+ const totalRecords = costResponse?.length || 0;
56
63
  const transformedData = lodash.reduce(
57
64
  costResponse,
58
65
  (accumulator, usageItem) => {
66
+ if (!usageItem.date || !usageItem.organizationName || !usageItem.sku || usageItem.netAmount === void 0 || usageItem.netAmount === null) {
67
+ filteredOutMissingFields++;
68
+ return accumulator;
69
+ }
70
+ const amount = functions.parseCost(usageItem.netAmount);
71
+ if (amount === 0) {
72
+ filteredOutZeroAmount++;
73
+ return accumulator;
74
+ }
59
75
  const billingPeriod = functions.getBillingPeriod(query.granularity, usageItem.date, "YYYY-MM-DDTHH:mm:ssZ");
60
76
  const account = usageItem.organizationName;
61
77
  const sku = usageItem.sku;
62
- const cost = usageItem.netAmount;
63
78
  const keyName = `${this.provider}/${account}/${sku}`;
64
79
  if (!accumulator[keyName]) {
80
+ uniqueKeys.add(keyName);
65
81
  accumulator[keyName] = {
66
82
  id: keyName,
67
83
  account: `${this.provider}/${account}`,
@@ -72,11 +88,21 @@ class GitHubClient extends InfraWalletClient.InfraWalletClient {
72
88
  reports: {}
73
89
  };
74
90
  }
75
- accumulator[keyName].reports[billingPeriod] = functions.parseCost(cost);
91
+ accumulator[keyName].reports[billingPeriod] = amount;
92
+ processedRecords++;
76
93
  return accumulator;
77
94
  },
78
95
  {}
79
96
  );
97
+ this.logTransformationSummary({
98
+ processed: processedRecords,
99
+ uniqueReports: uniqueKeys.size,
100
+ zeroAmount: filteredOutZeroAmount,
101
+ missingFields: filteredOutMissingFields,
102
+ invalidDate: filteredOutInvalidDate,
103
+ timeRange: filteredOutTimeRange,
104
+ totalRecords
105
+ });
80
106
  return Object.values(transformedData);
81
107
  }
82
108
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GitHubClient.cjs.js","sources":["../../src/cost-clients/GitHubClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport { CLOUD_PROVIDER, PROVIDER_TYPE } from '../service/consts';\nimport { getBillingPeriod, parseCost } from '../service/functions';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { GitHubBillingResponseSchema } from '../schemas/GitHubBilling';\nimport { ZodError } from 'zod';\n\nexport class GitHubClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new GitHubClient(CLOUD_PROVIDER.GITHUB, config, database, cache, logger);\n }\n\n protected async initCloudClient(_integrationConfig: Config): Promise<any> {\n return null;\n }\n\n protected async fetchCosts(integrationConfig: Config, _client: any, query: CostQuery): Promise<any> {\n const token = integrationConfig.getString('token');\n const organization = integrationConfig.getString('organization');\n\n const startYear = new Date(Number(query.startTime)).getUTCFullYear();\n const endYear = new Date(Number(query.endTime)).getUTCFullYear();\n\n let allUsageItems: any[] = [];\n for (let year = startYear; year <= endYear; year++) {\n const url = `https://api.github.com/organizations/${organization}/settings/billing/usage?year=${year}`;\n this.logger.info(`Fetching GitHub costs from ${url}`);\n\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n\n if (!response.ok) {\n this.logger.error(`Failed to fetch GitHub costs for year ${year}: ${response.statusText}`);\n continue;\n }\n\n const data = await response.json();\n\n try {\n GitHubBillingResponseSchema.parse(data);\n this.logger.debug(`GitHub billing response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`GitHub billing 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 (Array.isArray(data.usageItems)) {\n allUsageItems = allUsageItems.concat(data.usageItems);\n }\n }\n\n return allUsageItems;\n }\n\n protected async transformCostsData(\n _integrationConfig: Config,\n query: CostQuery,\n costResponse: any,\n ): Promise<Report[]> {\n const transformedData = reduce(\n costResponse,\n (accumulator: { [key: string]: Report }, usageItem) => {\n const billingPeriod = getBillingPeriod(query.granularity, usageItem.date, 'YYYY-MM-DDTHH:mm:ssZ');\n const account = usageItem.organizationName;\n const sku = usageItem.sku;\n const cost = usageItem.netAmount;\n const keyName = `${this.provider}/${account}/${sku}`;\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${account}`,\n service: this.convertServiceName(sku),\n category: 'Developer Tools',\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n };\n }\n\n accumulator[keyName].reports[billingPeriod] = parseCost(cost);\n\n return accumulator;\n },\n {},\n );\n\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","GitHubBillingResponseSchema","ZodError","reduce","getBillingPeriod","PROVIDER_TYPE","parseCost"],"mappings":"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"}
1
+ {"version":3,"file":"GitHubClient.cjs.js","sources":["../../src/cost-clients/GitHubClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport { CLOUD_PROVIDER, PROVIDER_TYPE } from '../service/consts';\nimport { getBillingPeriod, parseCost } from '../service/functions';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { GitHubBillingResponseSchema } from '../schemas/GitHubBilling';\nimport { ZodError } from 'zod';\n\nexport class GitHubClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new GitHubClient(CLOUD_PROVIDER.GITHUB, config, database, cache, logger);\n }\n\n protected async initCloudClient(_integrationConfig: Config): Promise<any> {\n return null;\n }\n\n protected async fetchCosts(integrationConfig: Config, _client: any, query: CostQuery): Promise<any> {\n const token = integrationConfig.getString('token');\n const organization = integrationConfig.getString('organization');\n\n const startYear = new Date(Number(query.startTime)).getUTCFullYear();\n const endYear = new Date(Number(query.endTime)).getUTCFullYear();\n\n let allUsageItems: any[] = [];\n for (let year = startYear; year <= endYear; year++) {\n const url = `https://api.github.com/organizations/${organization}/settings/billing/usage?year=${year}`;\n this.logger.info(`Fetching GitHub costs from ${url}`);\n\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n\n if (!response.ok) {\n this.logger.error(`Failed to fetch GitHub costs for year ${year}: ${response.statusText}`);\n continue;\n }\n\n const data = await response.json();\n\n try {\n GitHubBillingResponseSchema.parse(data);\n this.logger.debug(`GitHub billing response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`GitHub billing 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 (Array.isArray(data.usageItems)) {\n allUsageItems = allUsageItems.concat(data.usageItems);\n }\n }\n\n return allUsageItems;\n }\n\n protected async transformCostsData(\n _integrationConfig: Config,\n query: CostQuery,\n costResponse: any,\n ): Promise<Report[]> {\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 = costResponse?.length || 0;\n\n const transformedData = reduce(\n costResponse,\n (accumulator: { [key: string]: Report }, usageItem) => {\n // Check for missing fields\n if (\n !usageItem.date ||\n !usageItem.organizationName ||\n !usageItem.sku ||\n usageItem.netAmount === undefined ||\n usageItem.netAmount === null\n ) {\n filteredOutMissingFields++;\n return accumulator;\n }\n\n const amount = parseCost(usageItem.netAmount);\n\n // Check for zero amount\n if (amount === 0) {\n filteredOutZeroAmount++;\n return accumulator;\n }\n\n const billingPeriod = getBillingPeriod(query.granularity, usageItem.date, 'YYYY-MM-DDTHH:mm:ssZ');\n const account = usageItem.organizationName;\n const sku = usageItem.sku;\n const keyName = `${this.provider}/${account}/${sku}`;\n\n if (!accumulator[keyName]) {\n uniqueKeys.add(keyName);\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${account}`,\n service: this.convertServiceName(sku),\n category: 'Developer Tools',\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n };\n }\n\n accumulator[keyName].reports[billingPeriod] = amount;\n processedRecords++;\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","GitHubBillingResponseSchema","ZodError","reduce","parseCost","getBillingPeriod","PROVIDER_TYPE"],"mappings":"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"}
@@ -60,6 +60,9 @@ class InfraWalletClient {
60
60
  async fetchTagValues(_integrationConfig, _client, _query, _tagKey) {
61
61
  return { tagValues: [], provider: this.provider };
62
62
  }
63
+ logTransformationSummary(summary) {
64
+ functions.logTransformationSummary(this.logger, this.provider, summary);
65
+ }
63
66
  // Get aggregated unique tag keys across all accounts of this cloud provider
64
67
  async getTagKeys(query) {
65
68
  const integrationConfigs = this.config.getOptionalConfigArray(
@@ -1 +1 @@
1
- {"version":3,"file":"InfraWalletClient.cjs.js","sources":["../../src/cost-clients/InfraWalletClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { addMonths, endOfMonth, format, startOfMonth } from 'date-fns';\nimport { reduce } from 'lodash';\nimport { getWallet } from '../controllers/MetricSettingController';\nimport { CostItem, bulkInsertCostItems, countCostItems, getCostItems } from '../models/CostItem';\nimport {\n CACHE_CATEGORY,\n CLOUD_PROVIDER,\n GRANULARITY,\n NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS,\n PROVIDER_TYPE,\n} from '../service/consts';\nimport {\n getDefaultCacheTTL,\n getReportsFromCache,\n getTagKeysFromCache,\n getTagValuesFromCache,\n setReportsToCache,\n setTagKeysToCache,\n setTagValuesToCache,\n tagExists,\n usageDateToPeriodString,\n} from '../service/functions';\nimport {\n ClientResponse,\n CloudProviderError,\n CostQuery,\n Filter,\n Report,\n Tag,\n TagsQuery,\n TagsResponse,\n Wallet,\n} from '../service/types';\n\nexport abstract class InfraWalletClient {\n constructor(\n protected readonly provider: CLOUD_PROVIDER,\n protected readonly config: Config,\n protected readonly database: DatabaseService,\n protected readonly cache: CacheService,\n protected readonly logger: LoggerService,\n ) {}\n\n protected convertServiceName(serviceName: string): string {\n return `${this.provider}/${serviceName}`;\n }\n\n protected evaluateIntegrationFilters(account: string, integrationConfig: Config): boolean {\n const filters: Filter[] = [];\n for (const filter of integrationConfig.getOptionalConfigArray('filters') || []) {\n filters.push({\n type: filter.getString('type'),\n attribute: filter.getString('attribute'),\n pattern: filter.getString('pattern'),\n });\n }\n return this.evaluateFilters(account, filters);\n }\n\n private evaluateFilters(account: string, filters: Filter[]): boolean {\n if (!filters || filters.length === 0) {\n // include if no filter\n return true;\n }\n\n let included = false;\n let hasIncludeFilter = false;\n\n for (const filter of filters) {\n const regex = new RegExp(filter.pattern);\n\n if (filter.type === 'exclude' && regex.test(account)) {\n // exclude immediately if an exclude filter matches\n return false;\n }\n\n if (filter.type === 'include') {\n hasIncludeFilter = true;\n\n if (regex.test(account)) {\n included = true;\n }\n }\n }\n\n if (hasIncludeFilter) {\n return included;\n }\n\n return true;\n }\n\n protected abstract initCloudClient(integrationConfig: Config): Promise<any>;\n\n // Get all cost allocation tag keys from one account\n protected async fetchTagKeys(\n _integrationConfig: Config,\n _client: any,\n _query: TagsQuery,\n ): Promise<{ tagKeys: string[]; provider: CLOUD_PROVIDER }> {\n // To be implemented by each provider client\n return { tagKeys: [], provider: this.provider };\n }\n\n // Get all tag values of the specified tag key from one account\n protected async fetchTagValues(\n _integrationConfig: Config,\n _client: any,\n _query: TagsQuery,\n _tagKey: string,\n ): Promise<{ tagValues: string[]; provider: CLOUD_PROVIDER }> {\n // To be implemented by each provider client\n return { tagValues: [], provider: this.provider };\n }\n\n protected abstract fetchCosts(integrationConfig: Config, client: any, query: CostQuery): Promise<any>;\n\n protected abstract transformCostsData(\n integrationConfig: Config,\n query: CostQuery,\n costResponse: any,\n ): Promise<Report[]>;\n\n // Get aggregated unique tag keys across all accounts of this cloud provider\n async getTagKeys(query: TagsQuery): Promise<TagsResponse> {\n const integrationConfigs = this.config.getOptionalConfigArray(\n `backend.infraWallet.integrations.${this.provider.toLowerCase()}`,\n );\n if (!integrationConfigs) {\n return { tags: [], errors: [] };\n }\n\n const promises = [];\n const aggregatedTags: Tag[] = [];\n const errors: CloudProviderError[] = [];\n\n for (const integrationConfig of integrationConfigs) {\n const integrationName = integrationConfig.getString('name');\n\n const cachedTagKeys = await getTagKeysFromCache(this.cache, this.provider, integrationName, query);\n if (cachedTagKeys) {\n this.logger.info(`Reuse ${this.provider}/${integrationName} tag keys from cache`);\n\n for (const tag of cachedTagKeys) {\n if (!tagExists(aggregatedTags, tag)) {\n aggregatedTags.push(tag);\n }\n }\n\n continue;\n }\n\n const promise = (async () => {\n try {\n const client = await this.initCloudClient(integrationConfig);\n const response = await this.fetchTagKeys(integrationConfig, client, query);\n const tagKeysCache: Tag[] = [];\n\n for (const tagKey of response.tagKeys) {\n const tag = { key: tagKey, provider: response.provider };\n tagKeysCache.push(tag);\n\n if (!tagExists(aggregatedTags, tag)) {\n aggregatedTags.push(tag);\n }\n }\n await setTagKeysToCache(this.cache, tagKeysCache, this.provider, integrationName, query);\n } catch (e) {\n this.logger.error(e);\n errors.push({\n provider: this.provider,\n name: `${this.provider}/${integrationName}`,\n error: e.message,\n });\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n\n aggregatedTags.sort((a, b) => `${a.provider}/${a.key}`.localeCompare(`${b.provider}/${b.key}`));\n\n return {\n tags: aggregatedTags,\n errors: errors,\n };\n }\n\n // Get aggregated tag values of the specified tag key across all accounts of this cloud provider\n async getTagValues(query: TagsQuery, tagKey: string): Promise<TagsResponse> {\n const integrationConfigs = this.config.getOptionalConfigArray(\n `backend.infraWallet.integrations.${this.provider.toLowerCase()}`,\n );\n if (!integrationConfigs) {\n return { tags: [], errors: [] };\n }\n\n const promises = [];\n const aggregatedTags: Tag[] = [];\n const errors: CloudProviderError[] = [];\n\n for (const integrationConfig of integrationConfigs) {\n const integrationName = integrationConfig.getString('name');\n\n const cachedTagValues = await getTagValuesFromCache(this.cache, this.provider, integrationName, tagKey, query);\n if (cachedTagValues) {\n this.logger.info(`Reuse ${this.provider}/${integrationName}/${tagKey} tag values from cache`);\n\n for (const tag of cachedTagValues) {\n if (!tagExists(aggregatedTags, tag)) {\n aggregatedTags.push(tag);\n }\n }\n\n continue;\n }\n\n const promise = (async () => {\n try {\n const client = await this.initCloudClient(integrationConfig);\n const response = await this.fetchTagValues(integrationConfig, client, query, tagKey);\n const tagValuesCache: Tag[] = [];\n\n for (const tagValue of response.tagValues) {\n const tag = { key: tagKey, value: tagValue, provider: response.provider };\n tagValuesCache.push(tag);\n\n if (!tagExists(aggregatedTags, tag)) {\n aggregatedTags.push(tag);\n }\n }\n await setTagValuesToCache(this.cache, tagValuesCache, this.provider, integrationName, tagKey, query);\n } catch (e) {\n this.logger.error(e);\n errors.push({\n provider: this.provider,\n name: `${this.provider}/${integrationName}`,\n error: e.message,\n });\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n\n aggregatedTags.sort((a, b) =>\n `${a.provider}/${a.key}=${a.value}`.localeCompare(`${b.provider}/${b.key}=${b.value}`),\n );\n\n return {\n tags: aggregatedTags,\n errors: errors,\n };\n }\n\n async getCostReports(query: CostQuery): Promise<ClientResponse> {\n const autoloadCostData = this.config.getOptionalBoolean('backend.infraWallet.autoload.enabled') ?? false;\n const integrationConfigs = this.config.getOptionalConfigArray(\n `backend.infraWallet.integrations.${this.provider.toLowerCase()}`,\n );\n if (!integrationConfigs) {\n return { reports: [], errors: [] };\n }\n\n const results: Report[] = [];\n const errors: CloudProviderError[] = [];\n\n // if autoloadCostData enabled, for a query without any tags or groups, we get the results from the plugin database\n // skip Mock provider for autoloading data\n if (query.tags === '()' && query.groups === '' && autoloadCostData && this.provider !== CLOUD_PROVIDER.MOCK) {\n const reportsFromDatabase = await this.getCostReportsFromDatabase(query);\n reportsFromDatabase.forEach(report => {\n results.push(report);\n });\n } else {\n const promises = [];\n for (const integrationConfig of integrationConfigs) {\n const integrationName = integrationConfig.getString('name');\n\n // first check if there is any cached\n const cachedCosts = await getReportsFromCache(this.cache, this.provider, integrationName, query);\n if (cachedCosts) {\n this.logger.debug(`${this.provider}/${integrationName} costs from cache`);\n cachedCosts.forEach(cost => {\n results.push(cost);\n });\n continue;\n }\n\n const promise = (async () => {\n try {\n const client = await this.initCloudClient(integrationConfig);\n const costResponse = await this.fetchCosts(integrationConfig, client, query);\n\n const transformedReports = await this.transformCostsData(integrationConfig, query, costResponse);\n\n // cache the results\n await setReportsToCache(\n this.cache,\n transformedReports,\n this.provider,\n integrationName,\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}/${integrationName}`,\n error: e.message,\n });\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n }\n\n return {\n reports: results,\n errors: errors,\n };\n }\n\n async saveCostReportsToDatabase(wallet: Wallet, granularity: GRANULARITY): Promise<void> {\n const count = await countCostItems(this.database, wallet.id, this.provider, granularity);\n\n const endTime = endOfMonth(new Date());\n let startTime = startOfMonth(addMonths(new Date(), -1));\n if (count === 0) {\n // if there is no record, the first call is going to fetch the last 364 days' cost data\n // it cannot be 365 day or 1 year because Azure API will responds with the following error\n // Invalid query definition: The time period for pulling the data cannot exceed 1 year(s)\n startTime = startOfMonth(\n addMonths(new Date(), -1 * NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS[this.provider] + 1),\n );\n }\n\n this.logger.debug(`Fetching ${granularity} costs from ${startTime} to ${endTime} for ${this.provider}`);\n\n const results: Report[] = [];\n const usageDateFormat = granularity === GRANULARITY.DAILY ? 'yyyyMMdd' : 'yyyyMM';\n try {\n const clientResponse = await this.getCostReports({\n filters: '',\n tags: '',\n groups: '',\n granularity: granularity,\n startTime: startTime.getTime().toString(),\n endTime: endTime.getTime().toString(),\n });\n clientResponse.reports.forEach((cost: Report) => {\n results.push(cost);\n });\n } catch (e) {\n this.logger.error(e);\n }\n\n await bulkInsertCostItems(\n this.database,\n wallet.id,\n this.provider,\n granularity,\n parseInt(format(startTime, usageDateFormat), 10),\n parseInt(format(endTime, usageDateFormat), 10),\n results,\n );\n }\n\n async getCostReportsFromDatabase(query: CostQuery): Promise<Report[]> {\n // TODO: support searching for different wallets in the future, for now it is always the default wallet\n const defaultWallet = await getWallet(this.database, 'default');\n if (defaultWallet !== undefined) {\n // query the database\n const usageDateFormat = query.granularity === 'daily' ? 'yyyyMMdd' : 'yyyyMM';\n const startUsageDate = parseInt(format(parseInt(query.startTime, 10), usageDateFormat), 10);\n const endUsageDate = parseInt(format(parseInt(query.endTime, 10), usageDateFormat), 10);\n const costItems = await getCostItems(\n this.database,\n defaultWallet.id,\n this.provider,\n query.granularity,\n startUsageDate,\n endUsageDate,\n );\n\n // transform the cost items into cost reports\n const transformedData = reduce(\n costItems,\n (accumulator: { [key: string]: Report }, row: CostItem) => {\n const key = row.key;\n const otherColumns =\n typeof row.other_columns === 'string' ? JSON.parse(row.other_columns) : row.other_columns;\n\n if (!accumulator[key]) {\n accumulator[key] = {\n id: key,\n account: row.account,\n service: row.service,\n category: row.category,\n provider: row.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...otherColumns,\n };\n }\n accumulator[key].reports[usageDateToPeriodString(row.usage_date)] = parseFloat(row.cost as string);\n\n return accumulator;\n },\n {},\n );\n\n return Object.values(transformedData);\n }\n\n return [];\n }\n}\n"],"names":["getTagKeysFromCache","tagExists","setTagKeysToCache","getTagValuesFromCache","setTagValuesToCache","CLOUD_PROVIDER","getReportsFromCache","setReportsToCache","getDefaultCacheTTL","CACHE_CATEGORY","countCostItems","endOfMonth","startOfMonth","addMonths","NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS","GRANULARITY","bulkInsertCostItems","format","getWallet","getCostItems","reduce","PROVIDER_TYPE","usageDateToPeriodString"],"mappings":"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"}
1
+ {"version":3,"file":"InfraWalletClient.cjs.js","sources":["../../src/cost-clients/InfraWalletClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { addMonths, endOfMonth, format, startOfMonth } from 'date-fns';\nimport { reduce } from 'lodash';\nimport { getWallet } from '../controllers/MetricSettingController';\nimport { CostItem, bulkInsertCostItems, countCostItems, getCostItems } from '../models/CostItem';\nimport {\n CACHE_CATEGORY,\n CLOUD_PROVIDER,\n GRANULARITY,\n NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS,\n PROVIDER_TYPE,\n} from '../service/consts';\nimport {\n getDefaultCacheTTL,\n getReportsFromCache,\n getTagKeysFromCache,\n getTagValuesFromCache,\n logTransformationSummary,\n setReportsToCache,\n setTagKeysToCache,\n setTagValuesToCache,\n tagExists,\n usageDateToPeriodString,\n} from '../service/functions';\nimport {\n ClientResponse,\n CloudProviderError,\n CostQuery,\n Filter,\n Report,\n Tag,\n TagsQuery,\n TagsResponse,\n TransformationSummary,\n Wallet,\n} from '../service/types';\n\nexport abstract class InfraWalletClient {\n constructor(\n protected readonly provider: CLOUD_PROVIDER,\n protected readonly config: Config,\n protected readonly database: DatabaseService,\n protected readonly cache: CacheService,\n protected readonly logger: LoggerService,\n ) {}\n\n protected convertServiceName(serviceName: string): string {\n return `${this.provider}/${serviceName}`;\n }\n\n protected evaluateIntegrationFilters(account: string, integrationConfig: Config): boolean {\n const filters: Filter[] = [];\n for (const filter of integrationConfig.getOptionalConfigArray('filters') || []) {\n filters.push({\n type: filter.getString('type'),\n attribute: filter.getString('attribute'),\n pattern: filter.getString('pattern'),\n });\n }\n return this.evaluateFilters(account, filters);\n }\n\n private evaluateFilters(account: string, filters: Filter[]): boolean {\n if (!filters || filters.length === 0) {\n // include if no filter\n return true;\n }\n\n let included = false;\n let hasIncludeFilter = false;\n\n for (const filter of filters) {\n const regex = new RegExp(filter.pattern);\n\n if (filter.type === 'exclude' && regex.test(account)) {\n // exclude immediately if an exclude filter matches\n return false;\n }\n\n if (filter.type === 'include') {\n hasIncludeFilter = true;\n\n if (regex.test(account)) {\n included = true;\n }\n }\n }\n\n if (hasIncludeFilter) {\n return included;\n }\n\n return true;\n }\n\n protected abstract initCloudClient(integrationConfig: Config): Promise<any>;\n\n // Get all cost allocation tag keys from one account\n protected async fetchTagKeys(\n _integrationConfig: Config,\n _client: any,\n _query: TagsQuery,\n ): Promise<{ tagKeys: string[]; provider: CLOUD_PROVIDER }> {\n // To be implemented by each provider client\n return { tagKeys: [], provider: this.provider };\n }\n\n // Get all tag values of the specified tag key from one account\n protected async fetchTagValues(\n _integrationConfig: Config,\n _client: any,\n _query: TagsQuery,\n _tagKey: string,\n ): Promise<{ tagValues: string[]; provider: CLOUD_PROVIDER }> {\n // To be implemented by each provider client\n return { tagValues: [], provider: this.provider };\n }\n\n protected abstract fetchCosts(integrationConfig: Config, client: any, query: CostQuery): Promise<any>;\n\n protected abstract transformCostsData(\n integrationConfig: Config,\n query: CostQuery,\n costResponse: any,\n ): Promise<Report[]>;\n\n protected logTransformationSummary(summary: TransformationSummary): void {\n logTransformationSummary(this.logger, this.provider, summary);\n }\n\n // Get aggregated unique tag keys across all accounts of this cloud provider\n async getTagKeys(query: TagsQuery): Promise<TagsResponse> {\n const integrationConfigs = this.config.getOptionalConfigArray(\n `backend.infraWallet.integrations.${this.provider.toLowerCase()}`,\n );\n if (!integrationConfigs) {\n return { tags: [], errors: [] };\n }\n\n const promises = [];\n const aggregatedTags: Tag[] = [];\n const errors: CloudProviderError[] = [];\n\n for (const integrationConfig of integrationConfigs) {\n const integrationName = integrationConfig.getString('name');\n\n const cachedTagKeys = await getTagKeysFromCache(this.cache, this.provider, integrationName, query);\n if (cachedTagKeys) {\n this.logger.info(`Reuse ${this.provider}/${integrationName} tag keys from cache`);\n\n for (const tag of cachedTagKeys) {\n if (!tagExists(aggregatedTags, tag)) {\n aggregatedTags.push(tag);\n }\n }\n\n continue;\n }\n\n const promise = (async () => {\n try {\n const client = await this.initCloudClient(integrationConfig);\n const response = await this.fetchTagKeys(integrationConfig, client, query);\n const tagKeysCache: Tag[] = [];\n\n for (const tagKey of response.tagKeys) {\n const tag = { key: tagKey, provider: response.provider };\n tagKeysCache.push(tag);\n\n if (!tagExists(aggregatedTags, tag)) {\n aggregatedTags.push(tag);\n }\n }\n await setTagKeysToCache(this.cache, tagKeysCache, this.provider, integrationName, query);\n } catch (e) {\n this.logger.error(e);\n errors.push({\n provider: this.provider,\n name: `${this.provider}/${integrationName}`,\n error: e.message,\n });\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n\n aggregatedTags.sort((a, b) => `${a.provider}/${a.key}`.localeCompare(`${b.provider}/${b.key}`));\n\n return {\n tags: aggregatedTags,\n errors: errors,\n };\n }\n\n // Get aggregated tag values of the specified tag key across all accounts of this cloud provider\n async getTagValues(query: TagsQuery, tagKey: string): Promise<TagsResponse> {\n const integrationConfigs = this.config.getOptionalConfigArray(\n `backend.infraWallet.integrations.${this.provider.toLowerCase()}`,\n );\n if (!integrationConfigs) {\n return { tags: [], errors: [] };\n }\n\n const promises = [];\n const aggregatedTags: Tag[] = [];\n const errors: CloudProviderError[] = [];\n\n for (const integrationConfig of integrationConfigs) {\n const integrationName = integrationConfig.getString('name');\n\n const cachedTagValues = await getTagValuesFromCache(this.cache, this.provider, integrationName, tagKey, query);\n if (cachedTagValues) {\n this.logger.info(`Reuse ${this.provider}/${integrationName}/${tagKey} tag values from cache`);\n\n for (const tag of cachedTagValues) {\n if (!tagExists(aggregatedTags, tag)) {\n aggregatedTags.push(tag);\n }\n }\n\n continue;\n }\n\n const promise = (async () => {\n try {\n const client = await this.initCloudClient(integrationConfig);\n const response = await this.fetchTagValues(integrationConfig, client, query, tagKey);\n const tagValuesCache: Tag[] = [];\n\n for (const tagValue of response.tagValues) {\n const tag = { key: tagKey, value: tagValue, provider: response.provider };\n tagValuesCache.push(tag);\n\n if (!tagExists(aggregatedTags, tag)) {\n aggregatedTags.push(tag);\n }\n }\n await setTagValuesToCache(this.cache, tagValuesCache, this.provider, integrationName, tagKey, query);\n } catch (e) {\n this.logger.error(e);\n errors.push({\n provider: this.provider,\n name: `${this.provider}/${integrationName}`,\n error: e.message,\n });\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n\n aggregatedTags.sort((a, b) =>\n `${a.provider}/${a.key}=${a.value}`.localeCompare(`${b.provider}/${b.key}=${b.value}`),\n );\n\n return {\n tags: aggregatedTags,\n errors: errors,\n };\n }\n\n async getCostReports(query: CostQuery): Promise<ClientResponse> {\n const autoloadCostData = this.config.getOptionalBoolean('backend.infraWallet.autoload.enabled') ?? false;\n const integrationConfigs = this.config.getOptionalConfigArray(\n `backend.infraWallet.integrations.${this.provider.toLowerCase()}`,\n );\n if (!integrationConfigs) {\n return { reports: [], errors: [] };\n }\n\n const results: Report[] = [];\n const errors: CloudProviderError[] = [];\n\n // if autoloadCostData enabled, for a query without any tags or groups, we get the results from the plugin database\n // skip Mock provider for autoloading data\n if (query.tags === '()' && query.groups === '' && autoloadCostData && this.provider !== CLOUD_PROVIDER.MOCK) {\n const reportsFromDatabase = await this.getCostReportsFromDatabase(query);\n reportsFromDatabase.forEach(report => {\n results.push(report);\n });\n } else {\n const promises = [];\n for (const integrationConfig of integrationConfigs) {\n const integrationName = integrationConfig.getString('name');\n\n // first check if there is any cached\n const cachedCosts = await getReportsFromCache(this.cache, this.provider, integrationName, query);\n if (cachedCosts) {\n this.logger.debug(`${this.provider}/${integrationName} costs from cache`);\n cachedCosts.forEach(cost => {\n results.push(cost);\n });\n continue;\n }\n\n const promise = (async () => {\n try {\n const client = await this.initCloudClient(integrationConfig);\n const costResponse = await this.fetchCosts(integrationConfig, client, query);\n\n const transformedReports = await this.transformCostsData(integrationConfig, query, costResponse);\n\n // cache the results\n await setReportsToCache(\n this.cache,\n transformedReports,\n this.provider,\n integrationName,\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}/${integrationName}`,\n error: e.message,\n });\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n }\n\n return {\n reports: results,\n errors: errors,\n };\n }\n\n async saveCostReportsToDatabase(wallet: Wallet, granularity: GRANULARITY): Promise<void> {\n const count = await countCostItems(this.database, wallet.id, this.provider, granularity);\n\n const endTime = endOfMonth(new Date());\n let startTime = startOfMonth(addMonths(new Date(), -1));\n if (count === 0) {\n // if there is no record, the first call is going to fetch the last 364 days' cost data\n // it cannot be 365 day or 1 year because Azure API will responds with the following error\n // Invalid query definition: The time period for pulling the data cannot exceed 1 year(s)\n startTime = startOfMonth(\n addMonths(new Date(), -1 * NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS[this.provider] + 1),\n );\n }\n\n this.logger.debug(`Fetching ${granularity} costs from ${startTime} to ${endTime} for ${this.provider}`);\n\n const results: Report[] = [];\n const usageDateFormat = granularity === GRANULARITY.DAILY ? 'yyyyMMdd' : 'yyyyMM';\n try {\n const clientResponse = await this.getCostReports({\n filters: '',\n tags: '',\n groups: '',\n granularity: granularity,\n startTime: startTime.getTime().toString(),\n endTime: endTime.getTime().toString(),\n });\n clientResponse.reports.forEach((cost: Report) => {\n results.push(cost);\n });\n } catch (e) {\n this.logger.error(e);\n }\n\n await bulkInsertCostItems(\n this.database,\n wallet.id,\n this.provider,\n granularity,\n parseInt(format(startTime, usageDateFormat), 10),\n parseInt(format(endTime, usageDateFormat), 10),\n results,\n );\n }\n\n async getCostReportsFromDatabase(query: CostQuery): Promise<Report[]> {\n // TODO: support searching for different wallets in the future, for now it is always the default wallet\n const defaultWallet = await getWallet(this.database, 'default');\n if (defaultWallet !== undefined) {\n // query the database\n const usageDateFormat = query.granularity === 'daily' ? 'yyyyMMdd' : 'yyyyMM';\n const startUsageDate = parseInt(format(parseInt(query.startTime, 10), usageDateFormat), 10);\n const endUsageDate = parseInt(format(parseInt(query.endTime, 10), usageDateFormat), 10);\n const costItems = await getCostItems(\n this.database,\n defaultWallet.id,\n this.provider,\n query.granularity,\n startUsageDate,\n endUsageDate,\n );\n\n // transform the cost items into cost reports\n const transformedData = reduce(\n costItems,\n (accumulator: { [key: string]: Report }, row: CostItem) => {\n const key = row.key;\n const otherColumns =\n typeof row.other_columns === 'string' ? JSON.parse(row.other_columns) : row.other_columns;\n\n if (!accumulator[key]) {\n accumulator[key] = {\n id: key,\n account: row.account,\n service: row.service,\n category: row.category,\n provider: row.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...otherColumns,\n };\n }\n accumulator[key].reports[usageDateToPeriodString(row.usage_date)] = parseFloat(row.cost as string);\n\n return accumulator;\n },\n {},\n );\n\n return Object.values(transformedData);\n }\n\n return [];\n }\n}\n"],"names":["logTransformationSummary","getTagKeysFromCache","tagExists","setTagKeysToCache","getTagValuesFromCache","setTagValuesToCache","CLOUD_PROVIDER","getReportsFromCache","setReportsToCache","getDefaultCacheTTL","CACHE_CATEGORY","countCostItems","endOfMonth","startOfMonth","addMonths","NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS","GRANULARITY","bulkInsertCostItems","format","getWallet","getCostItems","reduce","PROVIDER_TYPE","usageDateToPeriodString"],"mappings":"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"}
@@ -55,10 +55,18 @@ class MockClient extends InfraWalletClient.InfraWalletClient {
55
55
  endDate = currentDate.clone();
56
56
  endDate.add(1, "day");
57
57
  }
58
+ let processedRecords = 0;
59
+ const filteredOutZeroAmount = 0;
60
+ const filteredOutMissingFields = 0;
61
+ const filteredOutInvalidDate = 0;
62
+ const filteredOutTimeRange = 0;
63
+ const uniqueKeys = /* @__PURE__ */ new Set();
64
+ const totalRecords = jsonData.length;
58
65
  const processedData = await Promise.all(
59
66
  jsonData.map(async (item) => {
60
67
  item.providerType = consts.PROVIDER_TYPE.INTEGRATION;
61
68
  item.reports = {};
69
+ uniqueKeys.add(item.id);
62
70
  let tempDate = moment__default.default(startDate);
63
71
  if (item.provider === "GCP") {
64
72
  tempDate = tempDate.add(1, "month");
@@ -76,11 +84,21 @@ class MockClient extends InfraWalletClient.InfraWalletClient {
76
84
  } else {
77
85
  item.reports[dateString] = this.getRandomValue(0.4, 33.3);
78
86
  }
87
+ processedRecords++;
79
88
  tempDate.add(1, step);
80
89
  }
81
90
  return item;
82
91
  })
83
92
  );
93
+ this.logTransformationSummary({
94
+ processed: processedRecords,
95
+ uniqueReports: uniqueKeys.size,
96
+ zeroAmount: filteredOutZeroAmount,
97
+ missingFields: filteredOutMissingFields,
98
+ invalidDate: filteredOutInvalidDate,
99
+ timeRange: filteredOutTimeRange,
100
+ totalRecords
101
+ });
84
102
  return processedData;
85
103
  } catch (err) {
86
104
  this.logger.error("Error while reading a file", err);
@@ -1 +1 @@
1
- {"version":3,"file":"MockClient.cjs.js","sources":["../../src/cost-clients/MockClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService, resolvePackagePath } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { promises as fsPromises } from 'fs';\nimport moment from 'moment';\nimport * as upath from 'upath';\nimport { CLOUD_PROVIDER, PROVIDER_TYPE } from '../service/consts';\nimport { cryptoRandom } from '../service/crypto';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\n\n// Helper function to generate cryptographically secure random numbers\n\nexport class MockClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new MockClient(CLOUD_PROVIDER.MOCK, config, database, cache, logger);\n }\n\n protected async initCloudClient(config: Config): Promise<any> {\n this.logger.debug(`MockClient.initCloudClient called with config: ${JSON.stringify(config)}`);\n\n return null;\n }\n\n protected async fetchCosts(_subAccountConfig: Config, _client: any, _query: CostQuery): Promise<any> {\n return null;\n }\n\n protected async transformCostsData(\n _subAccountConfig: Config,\n query: CostQuery,\n _costResponse: any,\n ): Promise<Report[]> {\n try {\n const startDate = moment.unix(Number(query.startTime) / 1000);\n let endDate = moment.unix(Number(query.endTime) / 1000);\n\n const mockDir = resolvePackagePath('@electrolux-oss/plugin-infrawallet-backend', 'mock');\n const mockFilePath = upath.join(mockDir, 'mock_response.json');\n const data = await fsPromises.readFile(mockFilePath, 'utf8');\n const jsonData: Report[] = JSON.parse(data);\n const currentDate = moment();\n\n if (endDate.isAfter(currentDate)) {\n endDate = currentDate.clone();\n endDate.add(1, 'day');\n }\n\n const processedData = await Promise.all(\n jsonData.map(async item => {\n item.providerType = PROVIDER_TYPE.INTEGRATION;\n item.reports = {};\n\n let tempDate = moment(startDate);\n if (item.provider === 'GCP') {\n // to simulate the scenario when there is no cost data for 1 month\n tempDate = tempDate.add(1, 'month');\n }\n\n let step: moment.unitOfTime.DurationConstructor = 'months';\n let dateFormat = 'YYYY-MM';\n\n if (query.granularity.toLowerCase() === 'daily') {\n step = 'days';\n dateFormat = 'YYYY-MM-DD';\n }\n\n while (tempDate.isBefore(endDate)) {\n const dateString = tempDate.format(dateFormat);\n\n if (query.granularity.toLowerCase() === 'monthly') {\n item.reports[dateString] = this.getRandomValue(0.4 * 30, 33.3 * 30);\n } else {\n item.reports[dateString] = this.getRandomValue(0.4, 33.3);\n }\n\n tempDate.add(1, step); // Step based on granularity\n }\n\n return item;\n }),\n );\n\n return processedData;\n } catch (err) {\n this.logger.error('Error while reading a file', err);\n throw err;\n }\n }\n\n getRandomValue(min: number, max: number): number {\n const random = cryptoRandom();\n const amplifiedRandom = Math.pow(random, 3);\n return amplifiedRandom * (max - min) + min;\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","moment","resolvePackagePath","upath","fsPromises","PROVIDER_TYPE","cryptoRandom"],"mappings":"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"}
1
+ {"version":3,"file":"MockClient.cjs.js","sources":["../../src/cost-clients/MockClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService, resolvePackagePath } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { promises as fsPromises } from 'fs';\nimport moment from 'moment';\nimport * as upath from 'upath';\nimport { CLOUD_PROVIDER, PROVIDER_TYPE } from '../service/consts';\nimport { cryptoRandom } from '../service/crypto';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\n\n// Helper function to generate cryptographically secure random numbers\n\nexport class MockClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new MockClient(CLOUD_PROVIDER.MOCK, config, database, cache, logger);\n }\n\n protected async initCloudClient(config: Config): Promise<any> {\n this.logger.debug(`MockClient.initCloudClient called with config: ${JSON.stringify(config)}`);\n\n return null;\n }\n\n protected async fetchCosts(_subAccountConfig: Config, _client: any, _query: CostQuery): Promise<any> {\n return null;\n }\n\n protected async transformCostsData(\n _subAccountConfig: Config,\n query: CostQuery,\n _costResponse: any,\n ): Promise<Report[]> {\n try {\n const startDate = moment.unix(Number(query.startTime) / 1000);\n let endDate = moment.unix(Number(query.endTime) / 1000);\n\n const mockDir = resolvePackagePath('@electrolux-oss/plugin-infrawallet-backend', 'mock');\n const mockFilePath = upath.join(mockDir, 'mock_response.json');\n const data = await fsPromises.readFile(mockFilePath, 'utf8');\n const jsonData: Report[] = JSON.parse(data);\n const currentDate = moment();\n\n if (endDate.isAfter(currentDate)) {\n endDate = currentDate.clone();\n endDate.add(1, 'day');\n }\n\n // Initialize tracking variables\n let processedRecords = 0;\n const filteredOutZeroAmount = 0;\n const filteredOutMissingFields = 0;\n const filteredOutInvalidDate = 0;\n const filteredOutTimeRange = 0;\n const uniqueKeys = new Set<string>();\n const totalRecords = jsonData.length;\n\n const processedData = await Promise.all(\n jsonData.map(async item => {\n item.providerType = PROVIDER_TYPE.INTEGRATION;\n item.reports = {};\n uniqueKeys.add(item.id);\n\n let tempDate = moment(startDate);\n if (item.provider === 'GCP') {\n // to simulate the scenario when there is no cost data for 1 month\n tempDate = tempDate.add(1, 'month');\n }\n\n let step: moment.unitOfTime.DurationConstructor = 'months';\n let dateFormat = 'YYYY-MM';\n\n if (query.granularity.toLowerCase() === 'daily') {\n step = 'days';\n dateFormat = 'YYYY-MM-DD';\n }\n\n while (tempDate.isBefore(endDate)) {\n const dateString = tempDate.format(dateFormat);\n\n if (query.granularity.toLowerCase() === 'monthly') {\n item.reports[dateString] = this.getRandomValue(0.4 * 30, 33.3 * 30);\n } else {\n item.reports[dateString] = this.getRandomValue(0.4, 33.3);\n }\n\n processedRecords++;\n tempDate.add(1, step); // Step based on granularity\n }\n\n return item;\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 processedData;\n } catch (err) {\n this.logger.error('Error while reading a file', err);\n throw err;\n }\n }\n\n getRandomValue(min: number, max: number): number {\n const random = cryptoRandom();\n const amplifiedRandom = Math.pow(random, 3);\n return amplifiedRandom * (max - min) + min;\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","moment","resolvePackagePath","upath","fsPromises","PROVIDER_TYPE","cryptoRandom"],"mappings":"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"}
@@ -116,7 +116,14 @@ class MongoAtlasClient extends InfraWalletClient.InfraWalletClient {
116
116
  });
117
117
  const lines = costResponse.split("\n");
118
118
  const header = lines[0].split(",");
119
- const rows = lines.slice(1);
119
+ const rows = lines.slice(1).filter((line) => line.trim());
120
+ let processedRecords = 0;
121
+ let filteredOutZeroAmount = 0;
122
+ let filteredOutMissingFields = 0;
123
+ let filteredOutInvalidDate = 0;
124
+ let filteredOutTimeRange = 0;
125
+ const uniqueKeys = /* @__PURE__ */ new Set();
126
+ const totalRecords = rows.length;
120
127
  const transformedData = lodash.reduce(
121
128
  rows,
122
129
  (accumulator, line) => {
@@ -125,36 +132,61 @@ class MongoAtlasClient extends InfraWalletClient.InfraWalletClient {
125
132
  header.forEach((columnName, index) => {
126
133
  rowData[columnName] = columns[index];
127
134
  });
135
+ if (!rowData.Amount || !rowData.Date || !rowData.SKU) {
136
+ filteredOutMissingFields++;
137
+ return accumulator;
138
+ }
128
139
  const amount = parseFloat(rowData.Amount) || 0;
129
- const billingPeriod = functions.getBillingPeriod(query.granularity, rowData.Date, "MM/DD/YYYY");
130
- const serviceName = rowData.SKU;
131
- const cluster = rowData.Cluster || "Unknown";
132
- const project = rowData.Project || "Unknown";
133
- const keyName = `${accountName}->${categoryMappingService.getCategoryByServiceName(
134
- this.provider,
135
- serviceName
136
- )}->${project}->${cluster}`;
137
- if (!accumulator[keyName]) {
138
- accumulator[keyName] = {
139
- id: keyName,
140
- account: `${this.provider}/${accountName}`,
141
- service: this.convertServiceName(serviceName),
142
- category: categoryMappingService.getCategoryByServiceName(this.provider, serviceName),
143
- provider: this.provider,
144
- providerType: consts.PROVIDER_TYPE.INTEGRATION,
145
- reports: {},
146
- ...{ project },
147
- ...{ cluster },
148
- ...tagKeyValues
149
- };
140
+ if (amount === 0) {
141
+ filteredOutZeroAmount++;
142
+ return accumulator;
150
143
  }
151
- if (!moment__default.default(billingPeriod).isBefore(moment__default.default(parseInt(query.startTime, 10)))) {
152
- accumulator[keyName].reports[billingPeriod] = (accumulator[keyName].reports[billingPeriod] || 0) + amount;
144
+ try {
145
+ const billingPeriod = functions.getBillingPeriod(query.granularity, rowData.Date, "MM/DD/YYYY");
146
+ const serviceName = rowData.SKU;
147
+ const cluster = rowData.Cluster || "Unknown";
148
+ const project = rowData.Project || "Unknown";
149
+ const keyName = `${accountName}->${categoryMappingService.getCategoryByServiceName(
150
+ this.provider,
151
+ serviceName
152
+ )}->${project}->${cluster}`;
153
+ if (!accumulator[keyName]) {
154
+ uniqueKeys.add(keyName);
155
+ accumulator[keyName] = {
156
+ id: keyName,
157
+ account: `${this.provider}/${accountName}`,
158
+ service: this.convertServiceName(serviceName),
159
+ category: categoryMappingService.getCategoryByServiceName(this.provider, serviceName),
160
+ provider: this.provider,
161
+ providerType: consts.PROVIDER_TYPE.INTEGRATION,
162
+ reports: {},
163
+ ...{ project },
164
+ ...{ cluster },
165
+ ...tagKeyValues
166
+ };
167
+ }
168
+ if (!moment__default.default(billingPeriod).isBefore(moment__default.default(parseInt(query.startTime, 10)))) {
169
+ accumulator[keyName].reports[billingPeriod] = (accumulator[keyName].reports[billingPeriod] || 0) + amount;
170
+ processedRecords++;
171
+ } else {
172
+ filteredOutTimeRange++;
173
+ }
174
+ } catch (error) {
175
+ filteredOutInvalidDate++;
153
176
  }
154
177
  return accumulator;
155
178
  },
156
179
  {}
157
180
  );
181
+ this.logTransformationSummary({
182
+ processed: processedRecords,
183
+ uniqueReports: uniqueKeys.size,
184
+ zeroAmount: filteredOutZeroAmount,
185
+ missingFields: filteredOutMissingFields,
186
+ invalidDate: filteredOutInvalidDate,
187
+ timeRange: filteredOutTimeRange,
188
+ totalRecords
189
+ });
158
190
  return Object.values(transformedData);
159
191
  }
160
192
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MongoAtlasClient.cjs.js","sources":["../../src/cost-clients/MongoAtlasClient.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 urllib from 'urllib';\nimport { CategoryMappingService } from '../service/CategoryMappingService';\nimport { CLOUD_PROVIDER, PROVIDER_TYPE } from '../service/consts';\nimport { getBillingPeriod } from '../service/functions';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { MongoAtlasInvoicesResponseSchema } from '../schemas/MongoAtlasBilling';\nimport { ZodError } from 'zod';\n\nexport class MongoAtlasClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new MongoAtlasClient(CLOUD_PROVIDER.MONGODB_ATLAS, config, database, cache, logger);\n }\n\n protected convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['Atlas'];\n\n for (const prefix of prefixes) {\n if (serviceName.startsWith(prefix)) {\n convertedName = serviceName.slice(prefix.length).trim();\n }\n }\n\n return `${this.provider}/${convertedName}`;\n }\n\n protected async initCloudClient(subAccountConfig: any): Promise<any> {\n const publicKey = subAccountConfig.getString('publicKey');\n const privateKey = subAccountConfig.getString('privateKey');\n\n const client = {\n digestAuth: `${publicKey}:${privateKey}`,\n };\n\n return client;\n }\n\n protected async fetchCosts(subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\n const orgId = subAccountConfig.getString('orgId');\n const invoicesUrl = `/orgs/${orgId}/invoices?fromDate=${moment(parseInt(query.startTime, 10)).format(\n 'YYYY-MM-DD',\n )}&toDate=${moment(parseInt(query.endTime, 10)).add(1, 'M').format('YYYY-MM-DD')}`;\n\n try {\n const fullInvoicesUrl = `https://cloud.mongodb.com/api/atlas/v2${invoicesUrl}`;\n const response = await urllib.request(fullInvoicesUrl, {\n ...client,\n method: 'GET',\n dataType: 'json',\n headers: {\n Accept: 'application/vnd.atlas.2023-01-01+json',\n },\n });\n\n if (response.status !== 200) {\n throw new Error(`Error fetching invoices: ${response.status} ${response.statusText}`);\n }\n\n try {\n MongoAtlasInvoicesResponseSchema.parse(response.data);\n this.logger.debug(`MongoDB Atlas invoices response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`MongoDB Atlas invoices 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 const invoices = response.data.results;\n\n const allInvoicesData = await Promise.all(\n invoices.map(async (invoice: any) => {\n const invoiceId = invoice.id;\n const csvUrl = `/orgs/${orgId}/invoices/${invoiceId}/csv`;\n const fullCsvUrl = `https://cloud.mongodb.com/api/atlas/v2${csvUrl}`;\n const csvResponse = await urllib.request(fullCsvUrl, {\n ...client,\n method: 'GET',\n dataType: 'text',\n headers: {\n Accept: 'application/vnd.atlas.2023-01-01+csv',\n },\n });\n\n if (csvResponse.status !== 200) {\n throw new Error(`Error fetching invoice CSV: ${csvResponse.status} ${csvResponse.statusText}`);\n }\n\n const lines = csvResponse.data.split('\\n');\n\n let foundOrganizationIdLine = false;\n\n // Discard rows from the beginning of the CSV up to and including the row starting with \"Organization ID\"\n const filteredLines = lines\n .filter((line: string) => {\n const trimmedLine = line.trim();\n if (trimmedLine.startsWith('Organization ID,')) {\n foundOrganizationIdLine = true;\n return false;\n }\n if (!foundOrganizationIdLine) {\n return false;\n }\n return trimmedLine !== '' && !trimmedLine.includes('Credit'); // Discard empty lines and lines where SKU is 'Credit'\n })\n .join('\\n');\n\n return filteredLines;\n }),\n );\n\n return allInvoicesData.join('\\n');\n } catch (error) {\n this.logger.error(`Error fetching invoices from MongoDB Atlas: ${error.message}`);\n throw error;\n }\n }\n\n protected async transformCostsData(\n subAccountConfig: Config,\n query: CostQuery,\n costResponse: string,\n ): Promise<Report[]> {\n const categoryMappingService = CategoryMappingService.getInstance();\n const accountName = subAccountConfig.getString('name');\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 lines = costResponse.split('\\n');\n const header = lines[0].split(',');\n const rows = lines.slice(1);\n\n const transformedData = reduce(\n rows,\n (accumulator: { [key: string]: Report }, line) => {\n const columns = line.split(',');\n const rowData: { [key: string]: string } = {};\n header.forEach((columnName, index) => {\n rowData[columnName] = columns[index];\n });\n\n const amount = parseFloat(rowData.Amount) || 0;\n const billingPeriod = getBillingPeriod(query.granularity, rowData.Date, 'MM/DD/YYYY');\n const serviceName = rowData.SKU;\n const cluster = rowData.Cluster || 'Unknown';\n const project = rowData.Project || 'Unknown';\n\n const keyName = `${accountName}->${categoryMappingService.getCategoryByServiceName(\n this.provider,\n serviceName,\n )}->${project}->${cluster}`;\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${accountName}`,\n service: this.convertServiceName(serviceName),\n category: categoryMappingService.getCategoryByServiceName(this.provider, serviceName),\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...{ project: project },\n ...{ cluster: cluster },\n ...tagKeyValues,\n };\n }\n\n if (!moment(billingPeriod).isBefore(moment(parseInt(query.startTime, 10)))) {\n accumulator[keyName].reports[billingPeriod] = (accumulator[keyName].reports[billingPeriod] || 0) + amount;\n }\n\n return accumulator;\n },\n {},\n );\n\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","moment","urllib","MongoAtlasInvoicesResponseSchema","ZodError","CategoryMappingService","reduce","getBillingPeriod","PROVIDER_TYPE"],"mappings":"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"}
1
+ {"version":3,"file":"MongoAtlasClient.cjs.js","sources":["../../src/cost-clients/MongoAtlasClient.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 urllib from 'urllib';\nimport { CategoryMappingService } from '../service/CategoryMappingService';\nimport { CLOUD_PROVIDER, PROVIDER_TYPE } from '../service/consts';\nimport { getBillingPeriod } from '../service/functions';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { MongoAtlasInvoicesResponseSchema } from '../schemas/MongoAtlasBilling';\nimport { ZodError } from 'zod';\n\nexport class MongoAtlasClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new MongoAtlasClient(CLOUD_PROVIDER.MONGODB_ATLAS, config, database, cache, logger);\n }\n\n protected convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['Atlas'];\n\n for (const prefix of prefixes) {\n if (serviceName.startsWith(prefix)) {\n convertedName = serviceName.slice(prefix.length).trim();\n }\n }\n\n return `${this.provider}/${convertedName}`;\n }\n\n protected async initCloudClient(subAccountConfig: any): Promise<any> {\n const publicKey = subAccountConfig.getString('publicKey');\n const privateKey = subAccountConfig.getString('privateKey');\n\n const client = {\n digestAuth: `${publicKey}:${privateKey}`,\n };\n\n return client;\n }\n\n protected async fetchCosts(subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\n const orgId = subAccountConfig.getString('orgId');\n const invoicesUrl = `/orgs/${orgId}/invoices?fromDate=${moment(parseInt(query.startTime, 10)).format(\n 'YYYY-MM-DD',\n )}&toDate=${moment(parseInt(query.endTime, 10)).add(1, 'M').format('YYYY-MM-DD')}`;\n\n try {\n const fullInvoicesUrl = `https://cloud.mongodb.com/api/atlas/v2${invoicesUrl}`;\n const response = await urllib.request(fullInvoicesUrl, {\n ...client,\n method: 'GET',\n dataType: 'json',\n headers: {\n Accept: 'application/vnd.atlas.2023-01-01+json',\n },\n });\n\n if (response.status !== 200) {\n throw new Error(`Error fetching invoices: ${response.status} ${response.statusText}`);\n }\n\n try {\n MongoAtlasInvoicesResponseSchema.parse(response.data);\n this.logger.debug(`MongoDB Atlas invoices response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`MongoDB Atlas invoices 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 const invoices = response.data.results;\n\n const allInvoicesData = await Promise.all(\n invoices.map(async (invoice: any) => {\n const invoiceId = invoice.id;\n const csvUrl = `/orgs/${orgId}/invoices/${invoiceId}/csv`;\n const fullCsvUrl = `https://cloud.mongodb.com/api/atlas/v2${csvUrl}`;\n const csvResponse = await urllib.request(fullCsvUrl, {\n ...client,\n method: 'GET',\n dataType: 'text',\n headers: {\n Accept: 'application/vnd.atlas.2023-01-01+csv',\n },\n });\n\n if (csvResponse.status !== 200) {\n throw new Error(`Error fetching invoice CSV: ${csvResponse.status} ${csvResponse.statusText}`);\n }\n\n const lines = csvResponse.data.split('\\n');\n\n let foundOrganizationIdLine = false;\n\n // Discard rows from the beginning of the CSV up to and including the row starting with \"Organization ID\"\n const filteredLines = lines\n .filter((line: string) => {\n const trimmedLine = line.trim();\n if (trimmedLine.startsWith('Organization ID,')) {\n foundOrganizationIdLine = true;\n return false;\n }\n if (!foundOrganizationIdLine) {\n return false;\n }\n return trimmedLine !== '' && !trimmedLine.includes('Credit'); // Discard empty lines and lines where SKU is 'Credit'\n })\n .join('\\n');\n\n return filteredLines;\n }),\n );\n\n return allInvoicesData.join('\\n');\n } catch (error) {\n this.logger.error(`Error fetching invoices from MongoDB Atlas: ${error.message}`);\n throw error;\n }\n }\n\n protected async transformCostsData(\n subAccountConfig: Config,\n query: CostQuery,\n costResponse: string,\n ): Promise<Report[]> {\n const categoryMappingService = CategoryMappingService.getInstance();\n const accountName = subAccountConfig.getString('name');\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 lines = costResponse.split('\\n');\n const header = lines[0].split(',');\n const rows = lines.slice(1).filter(line => line.trim());\n\n // Initialize tracking variables\n let processedRecords = 0;\n let filteredOutZeroAmount = 0;\n let filteredOutMissingFields = 0;\n let filteredOutInvalidDate = 0;\n let filteredOutTimeRange = 0;\n const uniqueKeys = new Set<string>();\n const totalRecords = rows.length;\n\n const transformedData = reduce(\n rows,\n (accumulator: { [key: string]: Report }, line) => {\n const columns = line.split(',');\n const rowData: { [key: string]: string } = {};\n header.forEach((columnName, index) => {\n rowData[columnName] = columns[index];\n });\n\n // Check for missing fields\n if (!rowData.Amount || !rowData.Date || !rowData.SKU) {\n filteredOutMissingFields++;\n return accumulator;\n }\n\n const amount = parseFloat(rowData.Amount) || 0;\n\n // Check for zero amount\n if (amount === 0) {\n filteredOutZeroAmount++;\n return accumulator;\n }\n\n try {\n const billingPeriod = getBillingPeriod(query.granularity, rowData.Date, 'MM/DD/YYYY');\n const serviceName = rowData.SKU;\n const cluster = rowData.Cluster || 'Unknown';\n const project = rowData.Project || 'Unknown';\n\n const keyName = `${accountName}->${categoryMappingService.getCategoryByServiceName(\n this.provider,\n serviceName,\n )}->${project}->${cluster}`;\n\n if (!accumulator[keyName]) {\n uniqueKeys.add(keyName);\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${accountName}`,\n service: this.convertServiceName(serviceName),\n category: categoryMappingService.getCategoryByServiceName(this.provider, serviceName),\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...{ project: project },\n ...{ cluster: cluster },\n ...tagKeyValues,\n };\n }\n\n if (!moment(billingPeriod).isBefore(moment(parseInt(query.startTime, 10)))) {\n accumulator[keyName].reports[billingPeriod] = (accumulator[keyName].reports[billingPeriod] || 0) + amount;\n processedRecords++;\n } else {\n filteredOutTimeRange++;\n }\n } catch (error) {\n filteredOutInvalidDate++;\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","moment","urllib","MongoAtlasInvoicesResponseSchema","ZodError","CategoryMappingService","reduce","getBillingPeriod","PROVIDER_TYPE"],"mappings":"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"}