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