From 8b3fd59a69efecdc558d46faad5420835ec25b6d Mon Sep 17 00:00:00 2001 From: Aravindus1 Date: Mon, 21 Jul 2025 15:12:27 +0530 Subject: [PATCH] adding data tot the dashboard --- package-lock.json | 21 ++++++++++++ package.json | 1 + queries/dashboard.js | 57 ++++++++++++++++++++++++++++++++ queries.js => queries/queries.js | 2 +- server.js | 53 ++++++++++++++++++++++++++++- 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 queries/dashboard.js rename queries.js => queries/queries.js (99%) diff --git a/package-lock.json b/package-lock.json index 7a92a1c..176cceb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@aws-sdk/client-athena": "^3.699.0", "@aws-sdk/client-s3": "^3.701.0", "@aws-sdk/credential-providers": "^3.699.0", + "@fastify/cors": "^11.0.1", "awsmetrics": "file:", "dotenv": "^16.4.5", "fastify": "^5.3.0", @@ -1041,6 +1042,26 @@ "fast-uri": "^3.0.0" } }, + "node_modules/@fastify/cors": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-11.0.1.tgz", + "integrity": "sha512-dmZaE7M1f4SM8ZZuk5RhSsDJ+ezTgI7v3HHRj8Ow9CneczsPLZV6+2j2uwdaSLn8zhTv6QV0F4ZRcqdalGx1pQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fastify-plugin": "^5.0.0", + "toad-cache": "^3.7.0" + } + }, "node_modules/@fastify/error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.0.0.tgz", diff --git a/package.json b/package.json index f55584e..0b1162e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@aws-sdk/client-athena": "^3.699.0", "@aws-sdk/client-s3": "^3.701.0", "@aws-sdk/credential-providers": "^3.699.0", + "@fastify/cors": "^11.0.1", "awsmetrics": "file:", "dotenv": "^16.4.5", "fastify": "^5.3.0", diff --git a/queries/dashboard.js b/queries/dashboard.js new file mode 100644 index 0000000..690d116 --- /dev/null +++ b/queries/dashboard.js @@ -0,0 +1,57 @@ + +import dotenv from "dotenv"; +dotenv.config(); + +export const productResourceUsageQuery = `SELECT DISTINCT + line_item_product_code AS productCode, + COALESCE(NULLIF(TRIM(product_region_code), ''), 'global') AS regionCode, + line_item_usage_account_id AS accountId, + line_item_resource_id AS resourceId, + line_item_usage_type AS usageType, + line_item_usage_amount AS usageAmount, + line_item_unblended_rate AS unblendedRate, + line_item_unblended_cost AS unblendedCost, + line_item_blended_rate AS blendedRate, + line_item_blended_cost AS blendedCost, + pricing_term AS pricingTerm, + pricing_unit AS pricingUnit, + pricing_rate_code AS pricingRateCode, + pricing_currency AS pricingCurrency, + line_item_usage_start_date AS startDate, + line_item_usage_end_date AS endDate +FROM ${process.env.ATHENA_CU_TABLE} +WHERE line_item_usage_account_id = '%accountId%' + AND COALESCE(NULLIF(TRIM(product_region_code), ''), 'global') = '%regionCode%' + AND LOWER(line_item_product_code) = LOWER('%productCode%') + AND line_item_resource_id = '%resourceId%' +ORDER BY line_item_usage_start_date ASC;`; + + +export const allProductUsageQuery = `SELECT DISTINCT + line_item_product_code AS productCode, + COALESCE(NULLIF(TRIM(product_region_code), ''), 'global') AS regionCode, + line_item_usage_account_id AS accountId, + line_item_resource_id AS resourceId, + line_item_usage_type AS usageType, + line_item_usage_amount AS usageAmount, + line_item_unblended_cost AS unblendedCost, + line_item_blended_cost AS blendedCost, + line_item_usage_start_date AS startDate, + line_item_usage_end_date AS endDate +FROM ${process.env.ATHENA_CU_TABLE} +ORDER BY productCode, regionCode, accountId, startDate ASC;`; + + + +// /accounts/cost-usage-summary +export const accountCostUsageSummaryQuery = ` + SELECT + line_item_usage_account_id AS accountId, + SUM(line_item_usage_amount) AS totalUsageAmount, + SUM(line_item_unblended_cost) AS totalUnblendedCost, + SUM(line_item_blended_cost) AS totalBlendedCost + FROM ${process.env.ATHENA_CU_TABLE} + GROUP BY line_item_usage_account_id + ORDER BY totalUnblendedCost DESC; +`; + diff --git a/queries.js b/queries/queries.js similarity index 99% rename from queries.js rename to queries/queries.js index 1a4f485..965f382 100644 --- a/queries.js +++ b/queries/queries.js @@ -60,7 +60,7 @@ WHERE line_item_usage_account_id = '%accountId%' export const productUsageByRegionYearQuery = `SELECT DISTINCT line_item_product_code AS productCode, COALESCE(NULLIF(TRIM(product_region_code), ''), 'global') AS regionCode, - line_item_usage_account_id AS accountId, + line_item_usage_account_id AS accountId, line_item_resource_id AS resourceId, line_item_usage_type AS usageType, line_item_usage_amount AS usageAmount, diff --git a/server.js b/server.js index 22c0191..4585fde 100644 --- a/server.js +++ b/server.js @@ -1,13 +1,21 @@ import Fastify from "fastify"; +import cors from "@fastify/cors"; import sequelizePlugin from "./plugins/sequelize.js"; import dotenv from "dotenv"; import { executeQueryAsync, retrieveResultsAsync } from "./services/athena.js"; dotenv.config(); -import * as queries from "./queries.js"; +import * as dashboard from "./queries/dashboard.js"; +import * as queries from "./queries/queries.js"; const server = Fastify({ logger: true }); server.register(sequelizePlugin); +await server.register(cors, { + origin: "*", +}); +// await server.register(cors, { +// origin: ["http://localhost:3000"], +// }); server.get("/", async (request, reply) => { const [results, metadata] = await server.sequelize.query('SELECT 1 + 2 AS result'); console.log(results); @@ -220,9 +228,52 @@ server.get("/invoices/:invoiceId/accounts/:accountId/products/:productCode/usage return results; }); +server.get("/accounts/:accountId/regions/:regionCode/products/:productCode/resources/:resourceId/usage", async (request, reply) => { + let regionCode = request.params.regionCode; + if (!regionCode || regionCode === 'global') { + regionCode = 'global'; + } + + const query = dashboard.productResourceUsageQuery + .replace('%accountId%', request.params.accountId) + .replace('%regionCode%', regionCode) + .replace('%productCode%', request.params.productCode) + .replace('%resourceId%', request.params.resourceId); + + try { + const queryExecutionId = await executeQueryAsync(query); + const results = await retrieveResultsAsync(queryExecutionId); + return results ?? []; + } catch (err) { + request.log.error(err); + return reply.status(500).send({ + error: 'Failed to fetch resource usage', + details: err.message, + }); + } +}); + +server.get("/products/usage", async (request, reply) => { + const query = dashboard.allProductUsageQuery; + const queryExecutionId = await executeQueryAsync(query); + const results = await retrieveResultsAsync(queryExecutionId); + return results; +}); + +server.get("/accounts/cost-usage-summary", async (request, reply) => { + const query = dashboard.accountCostUsageSummaryQuery; + const queryExecutionId = await executeQueryAsync(query); + const results = await retrieveResultsAsync(queryExecutionId); + return results; +}); + + + try { await server.listen({ port: 3000 }) } catch (err) { server.log.error(err); process.exit(1); } + +