From be57dbb933ae3b72cae79713c55a1a0e4eb673bf Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 5 Dec 2023 14:35:10 +0000 Subject: [PATCH] Show warnings about concurrency and runs on the plans page --- .../app/components/billing/UpgradePrompt.tsx | 12 +++-- .../app/presenters/OrgBillingPlanPresenter.ts | 50 +++++++++++++++++-- .../route.tsx | 44 ++++++++++++---- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/apps/webapp/app/components/billing/UpgradePrompt.tsx b/apps/webapp/app/components/billing/UpgradePrompt.tsx index c1f59d0b6..cdbf94916 100644 --- a/apps/webapp/app/components/billing/UpgradePrompt.tsx +++ b/apps/webapp/app/components/billing/UpgradePrompt.tsx @@ -1,9 +1,10 @@ import { ArrowUpCircleIcon } from "@heroicons/react/24/outline"; -import { LinkButton } from "../primitives/Buttons"; -import { Paragraph } from "../primitives/Paragraph"; -import { plansPath, organizationBillingPath } from "~/utils/pathBuilder"; import { MatchedOrganization } from "~/hooks/useOrganizations"; import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route"; +import { formatNumberCompact } from "~/utils/numberFormatter"; +import { plansPath } from "~/utils/pathBuilder"; +import { LinkButton } from "../primitives/Buttons"; +import { Paragraph } from "../primitives/Paragraph"; type UpgradePromptProps = { organization: MatchedOrganization; @@ -12,14 +13,15 @@ type UpgradePromptProps = { export function UpgradePrompt({ organization }: UpgradePromptProps) { const currentPlan = useCurrentPlan(); - if (!currentPlan || !currentPlan.usage.exceededRunCount) { + if (!currentPlan || !currentPlan.usage.exceededRunCount || !currentPlan.usage.runCountCap) { return null; } return (
- You have exceeded the monthly {currentPlan.usage.runCountCap} Runs limit + You have exceeded the monthly {formatNumberCompact(currentPlan.usage.runCountCap)} Runs + limit `WITH events AS ( + SELECT + re.event_time, + re.organization_id, + re.event_type, + SUM(re.event_type) OVER (PARTITION BY re.organization_id ORDER BY re.event_time) AS running_total + FROM + triggerdotdev_events.run_executions re + WHERE + re.organization_id = ${organization.id} + AND re.event_time >= DATE_TRUNC('month', + CURRENT_DATE) + ) + SELECT + organization_id, MAX(running_total) AS max_concurrent_runs + FROM + events + GROUP BY + organization_id;`; + + return { + plans, + maxConcurrency: + maxConcurrency.at(0) !== undefined + ? Number(maxConcurrency[0].max_concurrent_runs) + : undefined, + }; } } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx index 8e373f35c..1d8850d56 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx @@ -11,9 +11,12 @@ import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Callout } from "~/components/primitives/Callout"; import { Header2 } from "~/components/primitives/Headers"; import { featuresForRequest } from "~/features.server"; +import { useFeatures } from "~/hooks/useFeatures"; import { OrgBillingPlanPresenter } from "~/presenters/OrgBillingPlanPresenter"; import { Handle } from "~/utils/handle"; import { OrganizationParamsSchema, organizationBillingPath } from "~/utils/pathBuilder"; +import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route"; +import { formatNumberCompact } from "~/utils/numberFormatter"; export async function loader({ params, request }: LoaderFunctionArgs) { const { organizationSlug } = OrganizationParamsSchema.parse(params); @@ -24,12 +27,16 @@ export async function loader({ params, request }: LoaderFunctionArgs) { } const presenter = new OrgBillingPlanPresenter(); - const plans = await presenter.call({ slug: organizationSlug }); - if (!plans) { + const result = await presenter.call({ slug: organizationSlug, isManagedCloud }); + if (!result) { throw new Response(null, { status: 404 }); } - return typedjson({ plans, organizationSlug }); + return typedjson({ + plans: result.plans, + maxConcurrency: result.maxConcurrency, + organizationSlug, + }); } export const handle: Handle = { @@ -37,16 +44,33 @@ export const handle: Handle = { }; export default function Page() { - const { plans, organizationSlug } = useTypedLoaderData(); + const { plans, maxConcurrency, organizationSlug } = useTypedLoaderData(); + const currentPlan = useCurrentPlan(); + + const hitConcurrencyLimit = + currentPlan?.subscription?.limits.concurrentRuns && maxConcurrency + ? maxConcurrency >= currentPlan.subscription!.limits.concurrentRuns! + : false; + + const hitRunLimit = currentPlan?.usage?.runCountCap + ? currentPlan.usage.currentRunCount > currentPlan.usage.runCountCap + : false; return (
- - Some of your Runs are being queued because your Run concurrency is limited to 50. - - - You have exceeded the monthly 10,000 Runs limit. Upgrade to a paid plan before Nov 30. - + {hitConcurrencyLimit && ( + + Some of your Runs are being queued because your Run concurrency is limited to{" "} + {currentPlan?.subscription?.limits.concurrentRuns}. + + )} + {hitRunLimit && ( + + {`You have exceeded the monthly + ${formatNumberCompact(currentPlan!.subscription!.limits.runs!)} Runs limit. Upgrade so you + can continue to perform Runs.`} + + )}
Estimate your usage