Added the tasks table to usage
This commit is contained in:
@@ -8,6 +8,15 @@ type Options = {
|
||||
organizationId: string;
|
||||
};
|
||||
|
||||
export type TaskUsageItem = {
|
||||
taskIdentifier: string;
|
||||
runCount: number;
|
||||
averageDuration: number;
|
||||
averageCost: number;
|
||||
totalDuration: number;
|
||||
totalCost: number;
|
||||
};
|
||||
|
||||
export class UsagePresenter extends BasePresenter {
|
||||
public async call({ organizationId }: Options) {
|
||||
//periods
|
||||
@@ -18,6 +27,7 @@ export class UsagePresenter extends BasePresenter {
|
||||
endOfToday.setDate(endOfToday.getDate() + 1);
|
||||
endOfToday.setHours(23, 59, 59, 999);
|
||||
|
||||
//usage data from the platform
|
||||
const past30Days = getUsageSeries(organizationId, {
|
||||
from: thirtyDaysAgo,
|
||||
to: endOfToday,
|
||||
@@ -39,6 +49,40 @@ export class UsagePresenter extends BasePresenter {
|
||||
}));
|
||||
});
|
||||
|
||||
//usage by task
|
||||
const tasks = this._replica.$queryRaw<TaskUsageItem[]>`
|
||||
SELECT
|
||||
tr."taskIdentifier",
|
||||
COUNT(*) AS "runCount",
|
||||
AVG(tr."usageDurationMs") AS "averageDuration",
|
||||
SUM(tr."usageDurationMs") AS "totalDuration",
|
||||
AVG(tr."costInCents") / 100.0 AS "averageCost",
|
||||
SUM(tr."costInCents") / 100.0 AS "totalCost"
|
||||
FROM
|
||||
${sqlDatabaseSchema}."TaskRun" tr
|
||||
JOIN ${sqlDatabaseSchema}."Project" pr ON pr.id = tr."projectId"
|
||||
JOIN ${sqlDatabaseSchema}."Organization" org ON org.id = pr."organizationId"
|
||||
WHERE
|
||||
tr."createdAt" > ${thirtyDaysAgo}
|
||||
AND tr."createdAt" < ${endOfToday}
|
||||
|
||||
AND org.id = ${organizationId}
|
||||
GROUP BY
|
||||
tr."taskIdentifier"
|
||||
ORDER BY
|
||||
"totalCost" DESC;
|
||||
`.then((data) => {
|
||||
return data.map((item) => ({
|
||||
taskIdentifier: item.taskIdentifier,
|
||||
runCount: Number(item.runCount),
|
||||
averageDuration: Number(item.averageDuration),
|
||||
averageCost: Number(item.averageCost),
|
||||
totalDuration: Number(item.totalDuration),
|
||||
totalCost: Number(item.totalCost),
|
||||
}));
|
||||
});
|
||||
|
||||
//month-to-date usage data with projection
|
||||
const startOfMonth = new Date();
|
||||
startOfMonth.setDate(1);
|
||||
startOfMonth.setHours(0, 0, 0, 0);
|
||||
@@ -58,6 +102,7 @@ export class UsagePresenter extends BasePresenter {
|
||||
return {
|
||||
past30Days,
|
||||
usage,
|
||||
tasks,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,23 @@ import { featuresForRequest } from "~/features.server";
|
||||
import { getUsage, getUsageSeries } from "~/services/platform.v3.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { createTimeSeriesData } from "~/utils/graphs";
|
||||
import { formatCurrency } from "~/utils/numberFormatter";
|
||||
import { formatCurrency, formatNumber } from "~/utils/numberFormatter";
|
||||
import { OrganizationParamsSchema, organizationPath } from "~/utils/pathBuilder";
|
||||
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
|
||||
import { UsagePresenter } from "~/presenters/v3/UsagePresenter.server";
|
||||
import { Suspense } from "react";
|
||||
import { Await } from "@remix-run/react";
|
||||
import { Spinner } from "~/components/primitives/Spinner";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableHeaderCell,
|
||||
TableRow,
|
||||
} from "~/components/primitives/Table";
|
||||
import { formatDurationMilliseconds } from "@trigger.dev/core/v3";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
|
||||
export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
await requireUserId(request);
|
||||
@@ -37,11 +47,12 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
}
|
||||
|
||||
const presenter = new UsagePresenter();
|
||||
const { past30Days, usage } = await presenter.call({ organizationId: organization.id });
|
||||
const { past30Days, usage, tasks } = await presenter.call({ organizationId: organization.id });
|
||||
|
||||
return typeddefer({
|
||||
past30Days,
|
||||
usage,
|
||||
tasks,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,7 +74,7 @@ const tooltipStyle = {
|
||||
};
|
||||
|
||||
export default function ChoosePlanPage() {
|
||||
const { usage, past30Days } = useTypedLoaderData<typeof loader>();
|
||||
const { usage, past30Days, tasks } = useTypedLoaderData<typeof loader>();
|
||||
const currentPlan = useCurrentPlan();
|
||||
|
||||
return (
|
||||
@@ -177,6 +188,63 @@ export default function ChoosePlanPage() {
|
||||
</Await>
|
||||
</Suspense>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Header3 spacing>Tasks</Header3>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<Await resolve={tasks}>
|
||||
{(tasks) => {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Task</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Runs</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Average duration</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Average cost</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Total duration</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Total cost</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{tasks.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6}>
|
||||
<Paragraph variant="small">No runs to display yet.</Paragraph>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
tasks.map((task) => (
|
||||
<TableRow key={task.taskIdentifier}>
|
||||
<TableCell>{task.taskIdentifier}</TableCell>
|
||||
<TableCell alignment="right">
|
||||
{formatNumber(task.runCount)}
|
||||
</TableCell>
|
||||
<TableCell alignment="right">
|
||||
{formatDurationMilliseconds(task.averageDuration, {
|
||||
style: "short",
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell alignment="right">
|
||||
{formatCurrency(task.averageCost, false)}
|
||||
</TableCell>
|
||||
<TableCell alignment="right">
|
||||
{formatDurationMilliseconds(task.totalDuration, {
|
||||
style: "short",
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell alignment="right">
|
||||
{formatCurrency(task.totalCost, false)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}}
|
||||
</Await>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageBody>
|
||||
|
||||
@@ -19,12 +19,6 @@ export function createTimeSeriesData<R>({ startDate, endDate, window = "DAY", da
|
||||
const foundData = data.find((d) => {
|
||||
const time = d.date.getTime();
|
||||
const inRange = time >= periodStart.getTime() && time < periodEnd.getTime();
|
||||
console.log({
|
||||
time,
|
||||
periodStart: periodStart.getTime(),
|
||||
periodEnd: periodEnd.getTime(),
|
||||
inRange,
|
||||
});
|
||||
return inRange;
|
||||
});
|
||||
if (!foundData) {
|
||||
|
||||
Reference in New Issue
Block a user