Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion apps/webapp/app/components/billing/ConcurrentRunsChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,13 @@ export function ConcurrentRunsChart({
>
<Label value="Last 30 days" offset={-8} position="insideBottom" fill="#94A3B8" />
</XAxis>
<YAxis stroke="#94A3B8" fontSize={12} tickLine={false} axisLine={false} />
<YAxis
stroke="#94A3B8"
fontSize={12}
tickLine={false}
axisLine={false}
allowDecimals={false}
/>
<Tooltip
cursor={{ fill: "rgba(255,255,255,0.05)" }}
contentStyle={tooltipStyle}
Expand Down
89 changes: 89 additions & 0 deletions apps/webapp/app/components/billing/DailyRunsChat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Label, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
import { Paragraph } from "../primitives/Paragraph";

const tooltipStyle = {
display: "flex",
alignItems: "center",
gap: "0.5rem",
borderRadius: "0.25rem",
border: "1px solid #1A2434",
backgroundColor: "#0B1018",
padding: "0.3rem 0.5rem",
fontSize: "0.75rem",
color: "#E2E8F0",
};

type DataItem = { date: string; runs: number };

const dateFormatter = new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
});

export function DailyRunsChart({
data,
hasDailyRunsData,
}: {
data: DataItem[];
hasDailyRunsData: boolean;
}) {
return (
<div className="relative">
{!hasDailyRunsData && (
<Paragraph className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
No daily Runs to show
</Paragraph>
)}
<ResponsiveContainer width="100%" height="100%" className="relative min-h-[20rem]">
<LineChart
data={data}
margin={{
top: 20,
right: 0,
left: 0,
bottom: 10,
}}
className="-ml-8"
>
<XAxis
stroke="#94A3B8"
fontSize={12}
tickLine={false}
axisLine={false}
dataKey={(item: DataItem) => {
if (!item.date) return "";
const date = new Date(item.date);
if (date.getDate() === 1) {
return dateFormatter.format(date);
}
return `${date.getDate()}`;
}}
className="text-xs"
>
<Label value="Last 30 days" offset={-8} position="insideBottom" fill="#94A3B8" />
</XAxis>
<YAxis
stroke="#94A3B8"
fontSize={12}
tickLine={false}
axisLine={false}
allowDecimals={false}
/>
<Tooltip
cursor={{ fill: "rgba(255,255,255,0.05)" }}
contentStyle={tooltipStyle}
labelFormatter={(value, data) => {
const dateString = data.at(0)?.payload.date;
if (!dateString) {
return "";
}

return dateFormatter.format(new Date(dateString));
}}
/>
<Line dataKey="runs" name="Runs" stroke="#16A34A" strokeWidth={2} dot={false} />
</LineChart>
</ResponsiveContainer>
</div>
);
}
44 changes: 39 additions & 5 deletions apps/webapp/app/presenters/OrgUsagePresenter.server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { estimate } from "@trigger.dev/billing";
import { formatDateTime } from "~/components/primitives/DateTime";
import { PrismaClient, prisma } from "~/db.server";
import { featuresForRequest } from "~/features.server";
import { BillingService } from "~/services/billing.server";
import { logger } from "~/services/logger.server";

export class OrgUsagePresenter {
#prismaClient: PrismaClient;
Expand Down Expand Up @@ -108,7 +106,7 @@ export class OrgUsagePresenter {

const ThirtyDaysAgo = new Date();
ThirtyDaysAgo.setDate(ThirtyDaysAgo.getDate() - 30);
ThirtyDaysAgo.setHours(0, 0, 0, 0);
ThirtyDaysAgo.setUTCHours(0, 0, 0, 0);

const hasConcurrencyData = concurrencyChartRawData.length > 0;
const concurrencyChartRawDataFilledIn = fillInMissingConcurrencyDays(
Expand All @@ -117,6 +115,13 @@ export class OrgUsagePresenter {
concurrencyChartRawData
);

const dailyRunsRawData = await this.#prismaClient.$queryRaw<
{ day: Date; runs: BigInt }[]
>`SELECT date_trunc('day', "createdAt") as day, COUNT(*) as runs FROM "JobRun" WHERE "organizationId" = ${organization.id} AND "createdAt" >= NOW() - INTERVAL '30 days' AND "internal" = FALSE GROUP BY day`;

const hasDailyRunsData = dailyRunsRawData.length > 0;
const dailyRunsDataFilledIn = fillInMissingDailyRuns(ThirtyDaysAgo, 31, dailyRunsRawData);

const endOfMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1);
endOfMonth.setDate(endOfMonth.getDate() - 1);
const projectedRunsCount = Math.round(
Expand Down Expand Up @@ -146,12 +151,12 @@ export class OrgUsagePresenter {

const periodStart = new Date();
periodStart.setDate(1);
periodStart.setHours(0, 0, 0, 0);
periodStart.setUTCHours(0, 0, 0, 0);

const periodEnd = new Date();
periodEnd.setDate(1);
periodEnd.setMonth(periodEnd.getMonth() + 1);
periodEnd.setHours(0, 0, 0, 0);
periodEnd.setUTCHours(0, 0, 0, 0);

return {
id: organization.id,
Expand All @@ -161,6 +166,8 @@ export class OrgUsagePresenter {
hasMonthlyRunData,
concurrencyData: concurrencyChartRawDataFilledIn,
hasConcurrencyData,
dailyRunsData: dailyRunsDataFilledIn,
hasDailyRunsData,
runCostEstimation,
projectedRunCostEstimation,
periodStart,
Expand Down Expand Up @@ -224,6 +231,33 @@ function fillInMissingConcurrencyDays(
return outputData;
}

function fillInMissingDailyRuns(
startDate: Date,
days: number,
data: Array<{ day: Date; runs: BigInt }>
) {
const outputData: Array<{ date: Date; runs: number }> = [];
for (let i = 0; i < days; i++) {
const date = new Date(startDate);
date.setDate(date.getDate() + i);

const foundData = data.find((d) => d.day.toISOString() === date.toISOString());
if (!foundData) {
outputData.push({
date,
runs: 0,
});
} else {
outputData.push({
date,
runs: Number(foundData.runs),
});
}
}

return outputData;
}

// Start month will be like 2023-03 and endMonth will be like 2023-10
// The result should be an array of months between these two months, including the start and end month
// So for example, if startMonth is 2023-03 and endMonth is 2023-10, the result should be:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ConcurrentRunsChart } from "~/components/billing/ConcurrentRunsChart";
import { UsageBar } from "~/components/billing/UsageBar";
import { LinkButton } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { DailyRunsChart } from "~/components/billing/DailyRunsChat";
import { DateTime } from "~/components/primitives/DateTime";
import { Header2, Header3 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
Expand Down Expand Up @@ -104,6 +105,16 @@ export default function Page() {
</div>
</div>

<div>
<Header2 spacing>Daily runs</Header2>
<div className="flex w-full flex-col gap-5 rounded border border-border p-6">
<DailyRunsChart
data={data.dailyRunsData}
hasDailyRunsData={data.hasDailyRunsData}
/>
</div>
</div>

<div className="@container">
<Header2 spacing>Runs</Header2>
<div className="flex flex-col gap-5 rounded border border-border p-6">
Expand Down