Skip to content

Commit 8b31871

Browse files
Usage billing alerts (#2323)
* First draft billing alerts page * Budget alert form working * Don't let free plan users change the billing alert amount * Fix missing key in map in the form * Disable queues/org from admin API endpoint * Don't allow resuming if runsEnabled is false * Refer to "Billing alerts" not "Plans" Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Form missing dependencies fix Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Deal with thrown errors, fix for duplicating email fields * Added a RuntimeEnvironment organizationId index --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 14dcc76 commit 8b31871

File tree

17 files changed

+512
-35
lines changed

17 files changed

+512
-35
lines changed

apps/webapp/app/components/billing/UpgradePrompt.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ export function UpgradePrompt() {
3030
<Icon icon={ExclamationCircleIcon} className="h-5 w-5 text-error" />
3131
<Paragraph variant="small" className="text-error">
3232
You have exceeded the monthly $
33-
{(plan.v3Subscription?.plan?.limits.includedUsage ?? 500) / 100} free credits. No runs
34-
will execute in Prod until{" "}
33+
{(plan.v3Subscription?.plan?.limits.includedUsage ?? 500) / 100} free credits. Existing
34+
runs will be queued and new runs won't be created until{" "}
3535
<DateTime date={nextMonth} includeTime={false} timeZone="utc" />, or you upgrade.
3636
</Paragraph>
3737
</div>

apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
BellAlertIcon,
23
ChartBarIcon,
34
Cog8ToothIcon,
45
CreditCardIcon,
@@ -12,6 +13,7 @@ import {
1213
organizationSettingsPath,
1314
organizationTeamPath,
1415
rootPath,
16+
v3BillingAlertsPath,
1517
v3BillingPath,
1618
v3UsagePath,
1719
} from "~/utils/pathBuilder";
@@ -67,27 +69,34 @@ export function OrganizationSettingsSideMenu({
6769
<SideMenuHeader title="Organization" />
6870
</div>
6971
{isManagedCloud && (
70-
<SideMenuItem
71-
name="Usage"
72-
icon={ChartBarIcon}
73-
activeIconColor="text-indigo-500"
74-
to={v3UsagePath(organization)}
75-
data-action="usage"
76-
/>
77-
)}
78-
{isManagedCloud && (
79-
<SideMenuItem
80-
name="Billing"
81-
icon={CreditCardIcon}
82-
activeIconColor="text-emerald-500"
83-
to={v3BillingPath(organization)}
84-
data-action="billing"
85-
badge={
86-
currentPlan?.v3Subscription?.isPaying ? (
87-
<Badge variant="extra-small">{currentPlan?.v3Subscription?.plan?.title}</Badge>
88-
) : undefined
89-
}
90-
/>
72+
<>
73+
<SideMenuItem
74+
name="Usage"
75+
icon={ChartBarIcon}
76+
activeIconColor="text-indigo-500"
77+
to={v3UsagePath(organization)}
78+
data-action="usage"
79+
/>
80+
<SideMenuItem
81+
name="Billing"
82+
icon={CreditCardIcon}
83+
activeIconColor="text-emerald-500"
84+
to={v3BillingPath(organization)}
85+
data-action="billing"
86+
badge={
87+
currentPlan?.v3Subscription?.isPaying ? (
88+
<Badge variant="extra-small">{currentPlan?.v3Subscription?.plan?.title}</Badge>
89+
) : undefined
90+
}
91+
/>
92+
<SideMenuItem
93+
name="Billing alerts"
94+
icon={BellAlertIcon}
95+
activeIconColor="text-rose-500"
96+
to={v3BillingAlertsPath(organization)}
97+
data-action="billing-alerts"
98+
/>
99+
</>
91100
)}
92101
<SideMenuItem
93102
name="Team"

apps/webapp/app/components/primitives/Input.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const containerBase =
77
"has-[:focus-visible]:outline-none has-[:focus-visible]:ring-1 has-[:focus-visible]:ring-charcoal-650 has-[:focus-visible]:ring-offset-0 has-[:focus]:border-ring has-[:focus]:outline-none has-[:focus]:ring-1 has-[:focus]:ring-ring has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 ring-offset-background transition cursor-text";
88

99
const inputBase =
10-
"h-full w-full text-text-bright bg-transparent file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed outline-none ring-0 border-none";
10+
"h-full w-full text-text-bright bg-transparent file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed outline-none ring-0 border-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:m-0 [&::-webkit-inner-spin-button]:m-0 [&]:[-moz-appearance:textfield]";
1111

1212
const variants = {
1313
large: {

apps/webapp/app/presenters/v3/EnvironmentQueuePresenter.server.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type Environment = {
88
queued: number;
99
concurrencyLimit: number;
1010
burstFactor: number;
11+
runsEnabled: boolean;
1112
};
1213

1314
export class EnvironmentQueuePresenter extends BasePresenter {
@@ -23,11 +24,25 @@ export class EnvironmentQueuePresenter extends BasePresenter {
2324
const running = (engineV1Executing ?? 0) + (engineV2Executing ?? 0);
2425
const queued = (engineV1Queued ?? 0) + (engineV2Queued ?? 0);
2526

27+
const organization = await this._replica.organization.findFirst({
28+
where: {
29+
id: environment.organizationId,
30+
},
31+
select: {
32+
runsEnabled: true,
33+
},
34+
});
35+
36+
if (!organization) {
37+
throw new Error("Organization not found");
38+
}
39+
2640
return {
2741
running,
2842
queued,
2943
concurrencyLimit: environment.maximumConcurrencyLimit,
3044
burstFactor: environment.concurrencyLimitBurstFactor.toNumber(),
45+
runsEnabled: environment.type === "DEVELOPMENT" || organization.runsEnabled,
3146
};
3247
}
3348
}

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ export default function Page() {
285285
>
286286
View runs
287287
</LinkButton>
288-
<EnvironmentPauseResumeButton env={env} />
288+
{environment.runsEnabled ? <EnvironmentPauseResumeButton env={env} /> : null}
289289
</div>
290290
}
291291
valueClassName={env.paused ? "text-warning" : undefined}

0 commit comments

Comments
 (0)