-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathUsagePresenter.server.ts
More file actions
148 lines (130 loc) · 4.31 KB
/
UsagePresenter.server.ts
File metadata and controls
148 lines (130 loc) · 4.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { PrismaClientOrTransaction, sqlDatabaseSchema } from "~/db.server";
import { env } from "~/env.server";
import { getUsage, getUsageSeries } from "~/services/platform.v3.server";
import { createTimeSeriesData } from "~/utils/graphs";
import { BasePresenter } from "./basePresenter.server";
import { DataPoint, linear } from "regression";
import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactory.server";
type Options = {
organizationId: string;
startDate: Date;
};
export type TaskUsageItem = {
taskIdentifier: string;
runCount: number;
averageDuration: number;
averageCost: number;
totalDuration: number;
totalCost: number;
totalBaseCost: number;
};
export type UsageSeriesData = {
date: string;
dollars: number;
}[];
export class UsagePresenter extends BasePresenter {
public async call({ organizationId, startDate }: Options) {
if (isNaN(startDate.getTime())) {
throw new Error("Invalid start date");
}
//month period
const startOfMonth = new Date(startDate);
startOfMonth.setUTCDate(1);
startOfMonth.setUTCHours(0, 0, 0, 0);
const endOfMonth = new Date(
startOfMonth.getFullYear(),
startOfMonth.getMonth() + 1,
0,
23,
59,
59,
999
);
//usage data from the platform
const usage = getUsageSeries(organizationId, {
from: startOfMonth,
to: endOfMonth,
window: "DAY",
}).then((data) => {
//we want to sum it to get the total usage
const current = (data?.data.reduce((acc, period) => acc + period.value, 0) ?? 0) / 100;
// Get the start day (the day the customer started using the product) or the first day of the month
const startDay = new Date(data?.data.at(0)?.windowStart ?? startOfMonth).getDate();
// We want to project so we convert the data into an array of tuples [dayNumber, value]
const projectionData =
data?.data.map((period, index) => {
// Each value should be the sum of the previous values + the current value
// Adjust the day number to start from 1 when the customer started using the product
return [
new Date(period.windowStart).getDate() - startDay + 1,
data.data.slice(0, index + 1).reduce((acc, period) => acc + period.value, 0) / 100,
] as DataPoint;
}) ?? ([] as DataPoint[]);
const result = linear(projectionData);
const [a, b] = result.equation;
// Adjust the total days in the month based on when the customer started
const totalDaysInMonth = endOfMonth.getDate() - startDay + 1;
const projected = a * totalDaysInMonth + b;
const overall = {
current,
projected,
};
//and create daily data for the graph
const timeSeries = createTimeSeriesData({
startDate: startOfMonth,
endDate: endOfMonth,
window: "DAY",
data: data
? data.data.map((period) => ({
date: new Date(period.windowStart),
value: period.value,
}))
: [],
}).map((period) => ({
date: period.date.toISOString(),
dollars: (period.value ?? 0) / 100,
}));
return {
overall,
timeSeries,
};
});
//usage by task
const tasks = await getTaskUsageByOrganization(
organizationId,
startOfMonth,
endOfMonth,
this._replica
);
return {
usage,
tasks,
};
}
}
async function getTaskUsageByOrganization(
organizationId: string,
startOfMonth: Date,
endOfMonth: Date,
replica: PrismaClientOrTransaction
) {
const clickhouse = await clickhouseFactory.getClickhouseForOrganization(organizationId, "standard");
const [queryError, tasks] = await clickhouse.taskRuns.getTaskUsageByOrganization({
startTime: startOfMonth.getTime(),
endTime: endOfMonth.getTime(),
organizationId,
});
if (queryError) {
throw queryError;
}
return tasks
.map((task) => ({
taskIdentifier: task.task_identifier,
runCount: Number(task.run_count),
averageDuration: Number(task.average_duration),
averageCost: Number(task.average_cost) + env.CENTS_PER_RUN / 100,
totalDuration: Number(task.total_duration),
totalCost: Number(task.total_cost) + Number(task.total_base_cost),
}))
.sort((a, b) => b.totalCost - a.totalCost);
}