PromucFlow_constructor/deploy/docker/utils/bin/estimate_billing.js
Arpit Mohan 1af9e2042f
chore: Adding the appsmithctl command to estimate the bill for an existing Appsmith user (#15311)
## Description

This PR adds to the `estimate_billing` option to the `appsmithctl` command. This will help existing users estimate their bill when they consider upgrading to the paid edition.

Usage: 
```
appsmithctl estimate_billing 

Options:
  --sessionPrice    The price per active session. Defaults to 0.3 
  --priceCap        The price cap for a user in a given month. Defaults to 15
```

A user can also run the command for various scenarios to determine the optimum capacity tier.

## Type of change

- New feature (non-breaking change which adds functionality)
- This change requires a documentation update

## How Has This Been Tested?

Manually

## Checklist:

- [ ] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
2022-07-20 10:33:47 +05:30

152 lines
4.1 KiB
JavaScript

const { MongoClient } = require("mongodb");
const { DateTime } = require("luxon");
const cliProgress = require("cli-progress");
var args = require("minimist")(process.argv.slice(2));
const MONGODB_URL = args.mongoUrl || process.env.APPSMITH_MONGODB_URI;
const SESSION_PRICE = args.sessionPrice || 0.3;
const PRICE_CAP_FOR_USER = args.priceCap || 15;
let BILL = {};
async function run() {
if (MONGODB_URL == undefined || MONGODB_URL.trim() === "") {
console.log(`
Did you forget to specify the mandatory parameter MongoDB URL?
Options:
--sessionPrice The price per active session. Defaults to 0.3
--priceCap The price cap for a user in a given month. Defaults to 15
`);
return;
}
const client = new MongoClient(MONGODB_URL);
try {
initializeBill();
await client.connect();
const uniqueEmails = await getUniqueEmails(client);
console.log("Got all unique users. Going to calculate the estimated bill.");
// Since this is can potentially be a long process, show a progress bar to the user
const progressBar = new cliProgress.SingleBar(
{
format: "{bar} {percentage}% | ETA: {eta}s | {value}/{total} users",
},
cliProgress.Presets.shades_classic
);
progressBar.start(uniqueEmails.emails.length, 0);
// For each user calculate their monthly bill and append it to the global BILL object
for (const email of uniqueEmails.emails) {
const billingEventsForUser = await getBillingEventsForUser(client, email);
calculateBillForUser(billingEventsForUser);
progressBar.increment();
}
progressBar.stop();
console.log("\nYour estimated monthly bill for Appsmith is:");
console.log(BILL);
} catch (e) {
console.error(e);
} finally {
await client.close();
}
}
/**
* Initialize the monthly bill amount to 0 for all months
*/
function initializeBill() {
BILL["January"] = 0;
BILL["February"] = 0;
BILL["March"] = 0;
BILL["April"] = 0;
BILL["May"] = 0;
BILL["June"] = 0;
BILL["July"] = 0;
BILL["August"] = 0;
BILL["September"] = 0;
BILL["October"] = 0;
BILL["November"] = 0;
BILL["December"] = 0;
}
/**
* Get all the unique user emails from the usagePulse collection
* @param {MongoClient} client
* @returns
*/
async function getUniqueEmails(client) {
const dbClient = await client.db();
const query = [
{
$group: { _id: null, emails: { $addToSet: "$email" } },
},
];
const aggCursor = dbClient.collection("usagePulse").aggregate(query);
for await (const doc of aggCursor) {
return doc;
}
}
/**
* This function returns all the billing events of a user based on 30 min active session window
*
* @param {MongoClient} client The MongoClient object
* @param {String} email The email ID of the user for whom we are fetching the billing events
* @returns Array of billing events
*/
async function getBillingEventsForUser(client, email) {
const dbClient = await client.db();
const query = [
{ $match: { email: email } },
{
$group: {
_id: {
$toDate: {
$subtract: [
{ $toLong: { $toDate: "$_id" } },
{ $mod: [{ $toLong: { $toDate: "$_id" } }, 1000 * 60 * 30] },
],
},
},
},
},
{ $sort: { _id: 1 } },
];
const aggCursor = dbClient.collection("usagePulse").aggregate(query);
let billingEvents = [];
for await (const doc of aggCursor) {
billingEvents.push(doc);
}
return billingEvents;
}
/**
* This function calculates the monthly bill for the user and appends it to the global BILL object
*
* @param {Array} billingEventsForUser
*/
function calculateBillForUser(billingEventsForUser) {
let user = {};
billingEventsForUser.forEach((event) => {
const time = event._id;
const dateTime = DateTime.fromJSDate(time);
if (!(dateTime.monthLong in user)) {
user[dateTime.monthLong] = 0;
}
if (user[dateTime.monthLong] < PRICE_CAP_FOR_USER) {
user[dateTime.monthLong] += SESSION_PRICE;
BILL[dateTime.monthLong] += SESSION_PRICE;
BILL[dateTime.monthLong] =
Math.round(BILL[dateTime.monthLong] * 100) / 100;
}
});
}
module.exports = {
run,
};