diff --git a/Common/Types/MeteredPlan/ProductType.ts b/Common/Types/MeteredPlan/ProductType.ts new file mode 100644 index 0000000000..4ebb112d9e --- /dev/null +++ b/Common/Types/MeteredPlan/ProductType.ts @@ -0,0 +1,8 @@ + enum ProductType { + Logs = 'Logs', + Traces = 'Traces', + Metrics = 'Metrics', + ActiveMonitoring = 'ActiveMonitoring', +} + +export default ProductType; \ No newline at end of file diff --git a/CommonServer/Services/BillingService.ts b/CommonServer/Services/BillingService.ts index a71d86b080..8a771d532d 100644 --- a/CommonServer/Services/BillingService.ts +++ b/CommonServer/Services/BillingService.ts @@ -1,5 +1,4 @@ import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import MeteredPlan from 'Common/Types/Billing/MeteredPlan'; import OneUptimeDate from 'Common/Types/Date'; import APIException from 'Common/Types/Exception/ApiException'; import BadDataException from 'Common/Types/Exception/BadDataException'; @@ -15,7 +14,7 @@ import BaseService from './BaseService'; import Email from 'Common/Types/Email'; import Dictionary from 'Common/Types/Dictionary'; import Errors from '../Utils/Errors'; -import { ProductType } from 'Model/Models/UsageBilling'; +import ProductType from 'Common/Types/MeteredPlan/ProductType'; export type SubscriptionItem = Stripe.SubscriptionItem; @@ -876,59 +875,6 @@ export class BillingService extends BaseService { 'Plan with productType ' + productType + ' not found' ); } - - public async getMeteredPlan(data: { - productType: ProductType; - projectId: ObjectID; - }): Promise { - if (data.productType === ProductType.ActiveMonitoring) { - return new MeteredPlan({ - priceId: this.getMeteredPlanPriceId(data.productType), - pricePerUnitInUSD: 1, - unitName: 'Active Monitor', - }); - } - - // const dataRetentionDays: number = - // await ProjectService.getTelemetryDataRetentionInDays( - // data.projectId - // ); - - const dataRetentionDays: number = 15; - - const dataRetentionMultiplier: number = 0.1; // if the retention is 10 days for example, the cost per GB will be 0.01$ per GB per day (0.10 * dataRetentionDays * dataRetentionMultiplier). - - if (data.productType === ProductType.Logs) { - return new MeteredPlan({ - priceId: this.getMeteredPlanPriceId(data.productType), - pricePerUnitInUSD: - 0.1 * dataRetentionDays * dataRetentionMultiplier, - unitName: `GB (${dataRetentionDays} days data retention)`, - }); - } - - if (data.productType === ProductType.Traces) { - return new MeteredPlan({ - priceId: this.getMeteredPlanPriceId(data.productType), - pricePerUnitInUSD: - 0.1 * dataRetentionDays * dataRetentionMultiplier, - unitName: `GB (${dataRetentionDays} days data retention)`, - }); - } - - if (data.productType === ProductType.Metrics) { - return new MeteredPlan({ - priceId: this.getMeteredPlanPriceId(data.productType), - pricePerUnitInUSD: - 0.1 * dataRetentionDays * dataRetentionMultiplier, - unitName: `GB (${dataRetentionDays} days data retention)`, - }); - } - - throw new BadDataException( - 'Plan with name ' + data.productType + ' not found' - ); - } } export default new BillingService(); diff --git a/CommonServer/Services/TelemetryUsageBillingService.ts b/CommonServer/Services/TelemetryUsageBillingService.ts index 8893fe78b4..080102a5d4 100644 --- a/CommonServer/Services/TelemetryUsageBillingService.ts +++ b/CommonServer/Services/TelemetryUsageBillingService.ts @@ -1,5 +1,6 @@ import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import Model, { ProductType } from 'Model/Models/TelemetryUsageBilling'; +import Model from 'Model/Models/TelemetryUsageBilling'; +import ProductType from 'Common/Types/MeteredPlan/ProductType'; import DatabaseService from './DatabaseService'; import ObjectID from 'Common/Types/ObjectID'; import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; @@ -7,10 +8,9 @@ import OneUptimeDate from 'Common/Types/Date'; import QueryHelper from '../Types/Database/QueryHelper'; import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; import { MeteredPlanUtil } from '../Types/Billing/MeteredPlan/AllMeteredPlans'; -import MeteredPlan from 'Common/Types/Billing/MeteredPlan'; -import ServerMeteredPlan from '../Types/Billing/MeteredPlan/ServerMeteredPlan'; import Decimal from 'Common/Types/Decimal'; import TelemetryMeteredPlan from '../Types/Billing/MeteredPlan/TelemetryMeteredPlan'; +import BadDataException from 'Common/Types/Exception/BadDataException'; export class Service extends DatabaseService { public constructor(postgresDatabase?: PostgresDatabase) { @@ -53,8 +53,13 @@ export class Service extends DatabaseService { dataIngestedInGB: number; retentionInDays: number; }): Promise { + + if(data.productType !== ProductType.Traces && data.productType !== ProductType.Metrics && data.productType !== ProductType.Logs) { + throw new BadDataException("This product type is not a telemetry product type."); + } + const serverMeteredPlan: TelemetryMeteredPlan = - MeteredPlanUtil.getTelemetryMeteredPlanByProductType(data.productType); + MeteredPlanUtil.getMeteredPlanByProductType(data.productType) as TelemetryMeteredPlan; const totalCostOfThisOperationInUSD: number = serverMeteredPlan.getTotalCostInUSD( @@ -95,8 +100,8 @@ export class Service extends DatabaseService { await this.updateOneById({ id: usageBilling.id, data: { - usageCount: new Decimal( - (usageBilling.usageCount?.value || 0) + data.usageCount + dataIngestedInGB: new Decimal( + (usageBilling.dataIngestedInGB?.value || 0) + data.dataIngestedInGB ), totalCostInUSD: new Decimal( (usageBilling.totalCostInUSD?.value || 0) + @@ -111,12 +116,16 @@ export class Service extends DatabaseService { const usageBilling: Model = new Model(); usageBilling.projectId = data.projectId; usageBilling.productType = data.productType; - usageBilling.usageCount = new Decimal(data.usageCount); + usageBilling.dataIngestedInGB = new Decimal(data.dataIngestedInGB); + usageBilling.telemetryServiceId = data.telemetryServiceId; + usageBilling.retainTelemetryDataForDays = data.retentionInDays; usageBilling.isReportedToBillingProvider = false; usageBilling.createdAt = OneUptimeDate.getCurrentDate(); + usageBilling.day = OneUptimeDate.getDateString( OneUptimeDate.getCurrentDate() ); + usageBilling.totalCostInUSD = new Decimal( totalCostOfThisOperationInUSD ); diff --git a/CommonServer/Tests/TestingUtils/Services/Helpers.ts b/CommonServer/Tests/TestingUtils/Services/Helpers.ts index fe92594b2c..b753f304e6 100644 --- a/CommonServer/Tests/TestingUtils/Services/Helpers.ts +++ b/CommonServer/Tests/TestingUtils/Services/Helpers.ts @@ -14,7 +14,7 @@ import { ChangePlan, CouponData, } from '../../TestingUtils/Services/Types'; -import { ProductType } from 'Model/Models/UsageBilling'; +import ProductType from 'Common/Types/MeteredPlan/ProductType'; /// @dev consider modifyfing the EnvirontmentConfig to use functions instead of constants so that we can mock them diff --git a/CommonServer/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.ts b/CommonServer/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.ts index 812508dcc1..7de07c1857 100644 --- a/CommonServer/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.ts +++ b/CommonServer/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.ts @@ -7,7 +7,7 @@ import PositiveNumber from 'Common/Types/PositiveNumber'; import ProjectService from '../../../Services/ProjectService'; import BillingService from '../../../Services/BillingService'; import Project from 'Model/Models/Project'; -import { ProductType } from 'Model/Models/UsageBilling'; +import ProductType from 'Common/Types/MeteredPlan/ProductType'; export default class ActiveMonitoringMeteredPlan extends ServerMeteredPlan { public override getProductType(): ProductType { diff --git a/CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans.ts b/CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans.ts index 1298fa3b4b..3d293987e6 100644 --- a/CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans.ts +++ b/CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans.ts @@ -1,27 +1,27 @@ import ActiveMonitoringMeteredPlanType from './ActiveMonitoringMeteredPlan'; import ServerMeteredPlan from './ServerMeteredPlan'; import TelemetryMeteredPlanType from './TelemetryMeteredPlan'; -import { ProductType as TelemetryProductType } from 'Model/Models/TelemetryUsageBilling'; import BadDataException from 'Common/Types/Exception/BadDataException'; +import ProductType from 'Common/Types/MeteredPlan/ProductType'; export const ActiveMonitoringMeteredPlan: ActiveMonitoringMeteredPlanType = new ActiveMonitoringMeteredPlanType(); export const LogDataIngestMeteredPlan: TelemetryMeteredPlanType = new TelemetryMeteredPlanType({ - productType: TelemetryProductType.Logs, + productType: ProductType.Logs, unitCostInUSD: 0.10 / 15, // 0.10 per 15 days per GB }); export const MetricsDataIngestMeteredPlan: TelemetryMeteredPlanType = new TelemetryMeteredPlanType({ - productType: TelemetryProductType.Metrics, + productType: ProductType.Metrics, unitCostInUSD: 0.10 / 15, // 0.10 per 15 days per GB }); export const TracesDataIngestMetredPlan: TelemetryMeteredPlanType = new TelemetryMeteredPlanType({ - productType: TelemetryProductType.Traces, + productType: ProductType.Traces, unitCostInUSD: 0.10 / 15, // 0.10 per 15 days per GB }); @@ -33,16 +33,19 @@ const AllMeteredPlans: Array = [ ]; export class MeteredPlanUtil { - public static getTelemetryMeteredPlanByProductType( - productType: TelemetryProductType - ): TelemetryMeteredPlanType { - if (productType === TelemetryProductType.Logs) { + public static getMeteredPlanByProductType( + productType: ProductType + ): ServerMeteredPlan { + if (productType === ProductType.Logs) { return LogDataIngestMeteredPlan; - } else if (productType === TelemetryProductType.Metrics) { + } else if (productType === ProductType.Metrics) { return MetricsDataIngestMeteredPlan; - } else if (productType === TelemetryProductType.Traces) { + } else if (productType === ProductType.Traces) { return TracesDataIngestMetredPlan; + } else if (productType === ProductType.ActiveMonitoring) { + return ActiveMonitoringMeteredPlan; } + throw new BadDataException(`Unknown product type ${productType}`); } } diff --git a/CommonServer/Types/Billing/MeteredPlan/ServerMeteredPlan.ts b/CommonServer/Types/Billing/MeteredPlan/ServerMeteredPlan.ts index 2e983c5ff9..c073617445 100644 --- a/CommonServer/Types/Billing/MeteredPlan/ServerMeteredPlan.ts +++ b/CommonServer/Types/Billing/MeteredPlan/ServerMeteredPlan.ts @@ -2,21 +2,13 @@ import NotImplementedException from 'Common/Types/Exception/NotImplementedExcept import ObjectID from 'Common/Types/ObjectID'; import BillingService from '../../../Services/BillingService'; import MeteredPlan from 'Common/Types/Billing/MeteredPlan'; -import { ProductType } from 'Model/Models/TelemetryUsageBilling'; +import ProductType from 'Common/Types/MeteredPlan/ProductType'; export default class ServerMeteredPlan { public getProductType(): ProductType { throw new NotImplementedException(); } - public async getCostByProjectId( - projectId: ObjectID, - quantity: number - ): Promise { - const meteredPlan: MeteredPlan = await this.getMeteredPlan(projectId); - return this.getCostByMeteredPlan(meteredPlan, quantity); - } - public getCostByMeteredPlan( meteredPlan: MeteredPlan, quantity: number @@ -24,13 +16,6 @@ export default class ServerMeteredPlan { return meteredPlan.getPricePerUnit() * quantity; } - public async getMeteredPlan(projectId: ObjectID): Promise { - return await BillingService.getMeteredPlan({ - projectId: projectId, - productType: this.getProductType(), - }); - } - public async reportQuantityToBillingProvider( _projectId: ObjectID, _options: { diff --git a/CommonServer/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts b/CommonServer/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts index bd3d1cc0e4..9fc8a164a2 100644 --- a/CommonServer/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts +++ b/CommonServer/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts @@ -3,9 +3,11 @@ import ObjectID from 'Common/Types/ObjectID'; import ProjectService from '../../../Services/ProjectService'; import BillingService from '../../../Services/BillingService'; import Project from 'Model/Models/Project'; -import TelemetryUsageBilling, { ProductType } from 'Model/Models/TelemetryUsageBilling'; +import TelemetryUsageBilling from 'Model/Models/TelemetryUsageBilling'; import TelemetryUsageBillingService from '../../../Services/TelemetryUsageBillingService'; import OneUptimeDate from 'Common/Types/Date'; +import ProductType from 'Common/Types/MeteredPlan/ProductType'; + export default class TelemetryMeteredPlan extends ServerMeteredPlan { private _productType!: ProductType; diff --git a/Ingestor/API/FluentIngest.ts b/Ingestor/API/FluentIngest.ts index f4f88076c6..983875a85b 100644 --- a/Ingestor/API/FluentIngest.ts +++ b/Ingestor/API/FluentIngest.ts @@ -1,4 +1,4 @@ -import { ProductType } from 'Model/Models/UsageBilling'; +import ProductType from 'Common/Types/MeteredPlan/ProductType'; import Express, { ExpressRequest, ExpressResponse, diff --git a/Ingestor/Middleware/TelemetryIngest.ts b/Ingestor/Middleware/TelemetryIngest.ts index 05ff6be6c1..ef51d37335 100644 --- a/Ingestor/Middleware/TelemetryIngest.ts +++ b/Ingestor/Middleware/TelemetryIngest.ts @@ -8,15 +8,17 @@ import BadRequestException from 'Common/Types/Exception/BadRequestException'; import GlobalCache from 'CommonServer/Infrastructure/GlobalCache'; import DiskSize from 'Common/Types/DiskSize'; import ObjectID from 'Common/Types/ObjectID'; -import UsageBillingService from 'CommonServer/Services/UsageBillingService'; -import { ProductType } from 'Model/Models/UsageBilling'; +import TelemetryUsageBillingService from 'CommonServer/Services/TelemetryUsageBillingService'; +import ProductType from 'Common/Types/MeteredPlan/ProductType'; import TelemetryServiceService from 'CommonServer/Services/TelemetryServiceService'; import TelemetryService from 'Model/Models/TelemetryService'; import logger from 'CommonServer/Utils/Logger'; +import { DEFAULT_RETENTION_IN_DAYS } from 'Model/Models/TelemetryUsageBilling'; export interface TelemetryRequest extends ExpressRequest { serviceId: ObjectID; // Service ID projectId: ObjectID; // Project ID + dataRententionInDays: number; // how long the data should be retained. productType: ProductType; // what is the product type of the request - logs, metrics or traces. } @@ -68,6 +70,7 @@ export default class TelemetryIngest { select: { _id: true, projectId: true, + retainTelemetryDataForDays: true, }, props: { isRoot: true, @@ -92,6 +95,7 @@ export default class TelemetryIngest { (req as TelemetryRequest).serviceId = service.id as ObjectID; (req as TelemetryRequest).projectId = service.projectId as ObjectID; + (req as TelemetryRequest).dataRententionInDays = service.retainTelemetryDataForDays || DEFAULT_RETENTION_IN_DAYS; } (req as TelemetryRequest).serviceId = ObjectID.fromString( @@ -102,10 +106,12 @@ export default class TelemetryIngest { ); // report to Usage Service. - UsageBillingService.updateUsageBilling({ + TelemetryUsageBillingService.updateUsageBilling({ projectId: (req as TelemetryRequest).projectId, productType: (req as TelemetryRequest).productType, - usageCount: sizeToGb, + dataIngestedInGB: sizeToGb, + telemetryServiceId: (req as TelemetryRequest).serviceId, + retentionInDays: (req as TelemetryRequest).dataRententionInDays, }).catch((err: Error) => { logger.error('Failed to update usage billing for OTel'); logger.error(err); diff --git a/Model/Models/TelemetryUsageBilling.ts b/Model/Models/TelemetryUsageBilling.ts index 983b2a6555..4211b18148 100644 --- a/Model/Models/TelemetryUsageBilling.ts +++ b/Model/Models/TelemetryUsageBilling.ts @@ -17,12 +17,11 @@ import IconProp from 'Common/Types/Icon/IconProp'; import Decimal from 'Common/Types/Decimal'; import BaseModel from 'Common/Models/BaseModel'; import TelemetryService from './TelemetryService'; +import ProductType from 'Common/Types/MeteredPlan/ProductType'; + + +export const DEFAULT_RETENTION_IN_DAYS: number = 15; -export enum ProductType { - Logs = 'Logs', - Traces = 'Traces', - Metrics = 'Metrics', -} @TenantColumn('projectId') @TableAccessControl({ @@ -169,7 +168,7 @@ export default class TelemetryUsageBilling extends BaseModel { type: ColumnType.Number, nullable: true, unique: false, - default: 15, + default: DEFAULT_RETENTION_IN_DAYS, }) public retainTelemetryDataForDays?: number = undefined;