feat: add bar charts for premium and AI governance add-on license usage (#23442)

Implemented with the help of Cursor agents using Figma MCP

Figma design:
https://www.figma.com/design/klGTlHSPQwI4KBvAMdebrx/Customer-Usage-Controls-for-AI-Governance-Add-On?node-id=448-7658&m=dev

<img width="1143" height="639" alt="Screenshot 2026-03-23 at 20 10 05"
src="https://github.com/user-attachments/assets/300d4d5d-aad2-49a9-bfdd-a329312e5fa8"
/>
This commit is contained in:
Jaayden Halko
2026-03-24 09:07:06 +00:00
committed by GitHub
parent 5d0734e005
commit 245ce91199
9 changed files with 478 additions and 181 deletions
@@ -0,0 +1,96 @@
import type { GetLicensesResponse } from "api/api";
import type { Feature } from "api/typesGenerated";
import dayjs from "dayjs";
function isPremiumLicense(license: GetLicensesResponse): boolean {
return license.claims.feature_set?.toLowerCase() === "premium";
}
/**
* Matches the license card add-on section: Premium license with the
* ai_governance add-on in claims.
*/
export function licenseShowsAiGovernanceAddOn(
license: GetLicensesResponse,
): boolean {
return (
isPremiumLicense(license) &&
Boolean(license.claims.addons?.includes("ai_governance"))
);
}
export function isLicenseApplicableForAiGovernanceOverage(
license: GetLicensesResponse,
aiGovernanceUserFeature: Feature | undefined,
): boolean {
const isExpired = dayjs
.unix(license.claims.license_expires)
.isBefore(dayjs());
const isNotYetValid =
license.claims.nbf !== undefined &&
dayjs.unix(license.claims.nbf).isAfter(dayjs());
const isAiGovernanceEntitlementInGracePeriod =
aiGovernanceUserFeature?.entitlement === "grace_period";
return (
!isNotYetValid && (!isExpired || isAiGovernanceEntitlementInGracePeriod)
);
}
export function hasAiGovernanceAddOnLicense(
licenses: GetLicensesResponse[] | undefined,
aiGovernanceUserFeature: Feature | undefined,
): boolean {
return (
licenses?.some(
(license) =>
licenseShowsAiGovernanceAddOn(license) &&
isLicenseApplicableForAiGovernanceOverage(
license,
aiGovernanceUserFeature,
),
) ?? false
);
}
/**
* Best-effort limit from license JWT claims when merged entitlements are not
* yet available. Uses the same per-license field as the add-on card.
*/
function aiGovernanceLimitFromLicenses(
licenses: GetLicensesResponse[],
aiGovernanceUserFeature: Feature | undefined,
): number | undefined {
const limits = licenses
.filter(
(license) =>
licenseShowsAiGovernanceAddOn(license) &&
isLicenseApplicableForAiGovernanceOverage(
license,
aiGovernanceUserFeature,
),
)
.map((license) => license.claims.features?.ai_governance_user_limit)
.filter((limit): limit is number => limit !== undefined);
return limits.length > 0 ? Math.max(...limits) : undefined;
}
/**
* Resolves the displayed AI governance user limit for summary charts.
*
* When the feature is not entitled yet, JWT claims can still carry the
* purchased add-on seat limit while entitlements may report limit 0.
*/
export function effectiveAiGovernanceLimitForUsageCard(
aiGovernanceUserFeature: Feature | undefined,
licenses: GetLicensesResponse[] | undefined,
): number | undefined {
const limitFromClaims = licenses
? aiGovernanceLimitFromLicenses(licenses, aiGovernanceUserFeature)
: undefined;
const limitFromEntitlements = aiGovernanceUserFeature?.limit;
return aiGovernanceUserFeature?.enabled === true
? (limitFromEntitlements ?? limitFromClaims)
: (limitFromClaims ?? limitFromEntitlements);
}
@@ -1,7 +1,59 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import type { GetLicensesResponse } from "api/api";
import { expect, within } from "storybook/test";
import { AIGovernanceUsersConsumption } from "./AIGovernanceUsersConsumptionChart";
const licenseWithAiGovernanceAddOn: GetLicensesResponse = {
id: 42,
uploaded_at: "1660104000",
expires_at: "3420244800",
uuid: "license-ai-gov-addon",
claims: {
trial: false,
all_features: true,
feature_set: "premium",
version: 1,
addons: ["ai_governance"],
features: { ai_governance_user_limit: 750 },
license_expires: 3420244800,
nbf: 1660104000,
},
};
const higherApplicableAiGovernanceLicense: GetLicensesResponse = {
id: 43,
uploaded_at: "1660104000",
expires_at: "3420244800",
uuid: "license-ai-gov-addon-higher",
claims: {
trial: false,
all_features: true,
feature_set: "premium",
version: 1,
addons: ["ai_governance"],
features: { ai_governance_user_limit: 900 },
license_expires: 3420244800,
nbf: 1660104000,
},
};
const nonApplicableAiGovernanceLicense: GetLicensesResponse = {
id: 44,
uploaded_at: "1660104000",
expires_at: "3420244800",
uuid: "license-ai-gov-addon-non-applicable",
claims: {
trial: false,
all_features: true,
feature_set: "enterprise",
version: 1,
addons: ["ai_governance"],
features: { ai_governance_user_limit: 1200 },
license_expires: 3420244800,
nbf: 1660104000,
},
};
const meta: Meta<typeof AIGovernanceUsersConsumption> = {
title:
"pages/DeploymentSettingsPage/LicensesSettingsPage/AIGovernanceUsersConsumptionChart",
@@ -11,8 +63,9 @@ const meta: Meta<typeof AIGovernanceUsersConsumption> = {
enabled: true,
entitlement: "entitled",
limit: 1000,
actual: 750,
actual: 512,
},
licenses: [],
},
};
@@ -33,10 +86,7 @@ export const Exceeded: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(canvas.getByText("1,200")).toBeInTheDocument();
await expect(
canvas.getByText(/of 1,000 Users Entitled/i),
).toBeInTheDocument();
await expect(canvas.getByText("Add-on exceeded")).toBeInTheDocument();
await expect(canvas.getByText("1,000")).toBeInTheDocument();
},
};
@@ -46,12 +96,52 @@ export const Disabled: Story = {
enabled: false,
entitlement: "not_entitled",
},
licenses: [],
},
};
export const NoFeature: Story = {
args: {
aiGovernanceUserFeature: undefined,
licenses: [],
},
};
/** Entitlements not enabled, but a Premium license lists the add-on and limit in JWT claims. */
export const UsageBarFromLicenseClaims: Story = {
args: {
aiGovernanceUserFeature: {
enabled: false,
entitlement: "not_entitled",
},
licenses: [licenseWithAiGovernanceAddOn],
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(canvas.getByText("750")).toBeInTheDocument();
await expect(
canvas.getByRole("heading", { name: "AI governance add-on usage" }),
).toBeInTheDocument();
},
};
/** Picks the highest applicable limit when multiple licenses are present. */
export const UsageBarUsesHighestApplicableLicenseLimit: Story = {
args: {
aiGovernanceUserFeature: {
enabled: false,
entitlement: "not_entitled",
},
licenses: [
licenseWithAiGovernanceAddOn,
higherApplicableAiGovernanceLicense,
nonApplicableAiGovernanceLicense,
],
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(canvas.getByText("900")).toBeInTheDocument();
await expect(canvas.queryByText("1,200")).not.toBeInTheDocument();
},
};
@@ -1,31 +1,40 @@
import type { GetLicensesResponse } from "api/api";
import type { Feature } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Button } from "components/Button/Button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "components/Collapsible/Collapsible";
import { Link } from "components/Link/Link";
import { ChevronRightIcon } from "lucide-react";
import type { FC } from "react";
import { cn } from "utils/cn";
import { docs } from "utils/docs";
import {
effectiveAiGovernanceLimitForUsageCard,
hasAiGovernanceAddOnLicense,
} from "./AIGovernanceLicensing";
import { SeatUsageBarCard } from "./SeatUsageBarCard";
interface AIGovernanceUsersConsumptionProps {
aiGovernanceUserFeature?: Feature;
licenses?: GetLicensesResponse[];
}
export const AIGovernanceUsersConsumption: FC<
AIGovernanceUsersConsumptionProps
> = ({ aiGovernanceUserFeature }) => {
// If no feature is provided or it's disabled, show disabled state
if (!aiGovernanceUserFeature?.enabled) {
> = ({ aiGovernanceUserFeature, licenses }) => {
const hasAddOnLicense = hasAiGovernanceAddOnLicense(
licenses,
aiGovernanceUserFeature,
);
const effectiveLimit = effectiveAiGovernanceLimitForUsageCard(
aiGovernanceUserFeature,
licenses,
);
const showUsageBar =
aiGovernanceUserFeature?.enabled === true ||
(hasAddOnLicense && effectiveLimit !== undefined);
if (!showUsageBar) {
return (
<div className="min-h-60 flex items-center justify-center rounded-lg border border-solid p-12">
<div className="flex flex-col gap-4 items-center justify-center">
<div className="flex flex-col gap-2 items-center justify-center">
<span className="text-base">Users with AI Governance Add-On</span>
<div className="flex items-center justify-center rounded-lg border border-solid p-4">
<div className="flex flex-col items-center justify-center">
<div className="flex flex-col items-center justify-center">
<span className="text-base">AI governance add-on usage</span>
<span className="text-content-secondary text-center max-w-[464px] mt-2">
AI Governance is not included in your current license. Contact{" "}
<Link href="mailto:sales@coder.com">sales</Link> to upgrade your
@@ -37,122 +46,11 @@ export const AIGovernanceUsersConsumption: FC<
);
}
const limit = aiGovernanceUserFeature.limit;
if (limit === undefined || limit < 0) {
return <ErrorAlert error="Invalid license usage limits" />;
}
const actual = aiGovernanceUserFeature.actual;
const isExceeded = actual !== undefined && actual > limit;
return (
<section
className={cn("border border-solid rounded", {
"border-border-destructive": isExceeded,
})}
>
<div className="p-4">
<Collapsible>
<header className="flex flex-col gap-2 items-start">
<h3 className="text-md m-0 font-medium">
Users with AI Governance Add-On
</h3>
<CollapsibleTrigger asChild>
<Button
className={`
h-auto p-0 border-0 bg-transparent font-medium text-content-secondary
hover:bg-transparent hover:text-content-primary
[&[data-state=open]_svg]:rotate-90
`}
>
<ChevronRightIcon />
Learn more
</Button>
</CollapsibleTrigger>
</header>
<CollapsibleContent
className={`
pt-2 pl-7 pr-5 space-y-4 font-medium max-w-[720px]
text-sm text-content-secondary
[&_p]:m-0 [&_ul]:m-0 [&_ul]:p-0 [&_ul]:list-none
`}
>
<p>
Users using AI features like{" "}
<Link
href={docs("/ai-coder/ai-bridge")}
target="_blank"
rel="noreferrer"
>
AI Bridge
</Link>
,{" "}
<Link
href={docs("/ai-coder/boundary/agent-boundary")}
target="_blank"
rel="noreferrer"
>
Boundary
</Link>
, or{" "}
<Link
href={docs("/ai-coder/tasks")}
target="_blank"
rel="noreferrer"
>
Tasks
</Link>
.
</p>
</CollapsibleContent>
</Collapsible>
</div>
<div className="px-6 py-12 border-0 border-t border-solid">
<div className="flex flex-col gap-4 text-center justify-center items-center">
{actual !== undefined ? (
<>
<div className="flex flex-col gap-2 text-center justify-center items-center">
<div
className={cn("text-3xl font-bold", {
"text-content-destructive": isExceeded,
})}
>
{actual.toLocaleString()}
</div>
<div className="text-sm text-content-secondary">
of {limit.toLocaleString()} Users Entitled
</div>
</div>
<div
className={cn("text-sm", {
"text-content-destructive font-medium": isExceeded,
"text-content-secondary": !isExceeded,
})}
>
{isExceeded ? "Add-on exceeded" : "Active"}
</div>
</>
) : (
<>
<div className="flex flex-col gap-2 text-center justify-center items-center">
<div className="text-3xl font-bold">
{limit.toLocaleString()}
</div>
<div className="text-sm text-content-secondary">
Users Entitled
</div>
</div>
<div className="text-sm text-content-secondary">
Additional analytics and measurements coming soon
</div>
</>
)}
</div>
</div>
</section>
<SeatUsageBarCard
title="AI governance add-on usage"
actual={aiGovernanceUserFeature?.actual}
limit={effectiveLimit}
/>
);
};
@@ -18,6 +18,10 @@ import { ChevronDownIcon, EllipsisVerticalIcon, TrashIcon } from "lucide-react";
import { type FC, useState } from "react";
import { cn } from "utils/cn";
import { AIGovernanceAddOnCard } from "./AIGovernanceAddOnCard";
import {
isLicenseApplicableForAiGovernanceOverage,
licenseShowsAiGovernanceAddOn,
} from "./AIGovernanceLicensing";
type LicenseCardProps = {
license: GetLicensesResponse;
@@ -61,21 +65,19 @@ export const LicenseCard: FC<LicenseCardProps> = ({
? "Premium"
: "Enterprise";
const hasExplicitAiGovernanceAddOn = (license.claims.addons ?? []).includes(
"ai_governance",
);
const isAiGovernanceEntitlementInGracePeriod =
aiGovernanceUserFeature?.entitlement === "grace_period";
const hasExplicitAiGovernanceAddOn = licenseShowsAiGovernanceAddOn(license);
// Overage/display checks only apply to licenses that are currently effective.
const isLicenseApplicableForAiGovernanceOverage =
!isNotYetValid && (!isExpired || isAiGovernanceEntitlementInGracePeriod);
const isLicenseApplicable = isLicenseApplicableForAiGovernanceOverage(
license,
aiGovernanceUserFeature,
);
// A license "wins" when its AI governance limit matches the merged limit.
const isWinningAiGovernanceLicense =
aiGovernanceMergedLimit !== undefined &&
aiGovernanceLimit > 0 &&
aiGovernanceLimit === aiGovernanceMergedLimit;
const canUseAiGovernanceUsageForThisLicense =
isLicenseApplicableForAiGovernanceOverage &&
isLicenseApplicable &&
hasExplicitAiGovernanceAddOn &&
isWinningAiGovernanceLicense;
// Show the add-on as exceeded only for the winning, active add-on license.
@@ -82,6 +82,9 @@ const LicensesSettingsPage: FC = () => {
showConfetti={confettiOn}
isLoading={isLoading}
isRefreshing={refreshEntitlementsMutation.isPending}
hasUserLimitEntitlementData={
entitlementsQuery.data?.features.user_limit !== undefined
}
userLimitActual={entitlementsQuery.data?.features.user_limit?.actual}
userLimitLimit={entitlementsQuery.data?.features.user_limit?.limit}
licenses={licenses}
@@ -1,28 +1,75 @@
import { chromatic } from "testHelpers/chromatic";
import { MockLicenseResponse } from "testHelpers/entities";
import type { Meta, StoryObj } from "@storybook/react-vite";
import type { Feature } from "api/typesGenerated";
import { expect, fn, within } from "storybook/test";
import LicensesSettingsPageView from "./LicensesSettingsPageView";
export default {
const meta: Meta<typeof LicensesSettingsPageView> = {
title: "pages/DeploymentSettingsPage/LicensesSettingsPageView",
parameters: { chromatic },
component: LicensesSettingsPageView,
};
const defaultArgs = {
showConfetti: false,
isLoading: false,
userLimitActual: 1,
userLimitLimit: 10,
licenses: MockLicenseResponse,
};
export const Default = {
args: defaultArgs,
};
export const Empty = {
args: {
...defaultArgs,
licenses: null,
showConfetti: false,
isLoading: false,
hasUserLimitEntitlementData: true,
userLimitActual: 1,
userLimitLimit: 10,
licenses: MockLicenseResponse,
isRemovingLicense: false,
isRefreshing: false,
removeLicense: fn(),
refreshEntitlements: fn(),
activeUsers: [{ date: "2024-01-01", count: 1 }],
managedAgentFeature: {
enabled: false,
entitlement: "not_entitled",
} satisfies Feature,
aiGovernanceUserFeature: {
enabled: false,
entitlement: "not_entitled",
} satisfies Feature,
},
};
export default meta;
type Story = StoryObj<typeof LicensesSettingsPageView>;
export const Default: Story = {};
export const Empty: Story = {
args: {
licenses: [],
},
};
/** Premium + AI governance usage bars; AI governance shows `SeatUsageBarCard` (not the not-entitled placeholder). */
export const ActiveAIGovernanceAddOnUsage: Story = {
args: {
userLimitActual: 1923,
userLimitLimit: 2500,
activeUsers: [
{ date: "2024-01-01", count: 100 },
{ date: "2024-02-01", count: 120 },
],
aiGovernanceUserFeature: {
enabled: true,
entitlement: "entitled",
limit: 1000,
actual: 512,
} satisfies Feature,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(
canvas.getByRole("heading", { name: "Seat usage" }),
).toBeInTheDocument();
await expect(canvas.getByText("1,923")).toBeInTheDocument();
await expect(canvas.getByText("2,500")).toBeInTheDocument();
await expect(
canvas.getByRole("heading", { name: "AI governance add-on usage" }),
).toBeInTheDocument();
await expect(canvas.getByText("512")).toBeInTheDocument();
await expect(canvas.getByText("1,000")).toBeInTheDocument();
},
};
@@ -20,15 +20,17 @@ import { useWindowSize } from "hooks/useWindowSize";
import { PlusIcon, RotateCwIcon } from "lucide-react";
import type { FC } from "react";
import Confetti from "react-confetti";
import { Link } from "react-router";
import { Link as RouterLink } from "react-router";
import { AIGovernanceUsersConsumption } from "./AIGovernanceUsersConsumptionChart";
import { LicenseCard } from "./LicenseCard";
import { LicenseSeatConsumptionChart } from "./LicenseSeatConsumptionChart";
import { ManagedAgentsConsumption } from "./ManagedAgentsConsumption";
import { SeatUsageBarCard } from "./SeatUsageBarCard";
type Props = {
showConfetti: boolean;
isLoading: boolean;
hasUserLimitEntitlementData: boolean;
userLimitActual?: number;
userLimitLimit?: number;
licenses?: GetLicensesResponse[];
@@ -44,6 +46,7 @@ type Props = {
const LicensesSettingsPageView: FC<Props> = ({
showConfetti,
isLoading,
hasUserLimitEntitlementData,
userLimitActual,
userLimitLimit,
licenses,
@@ -82,10 +85,10 @@ const LicensesSettingsPageView: FC<Props> = ({
<Stack direction="row" spacing={2}>
<Button variant="outline" asChild>
<Link to="/deployment/licenses/add">
<RouterLink to="/deployment/licenses/add">
<PlusIcon />
Add a license
</Link>
</RouterLink>
</Button>
<Tooltip>
<TooltipTrigger asChild>
@@ -156,25 +159,35 @@ const LicensesSettingsPageView: FC<Props> = ({
</div>
)}
{licenses && licenses.length > 0 && (
<LicenseSeatConsumptionChart
limit={userLimitLimit}
data={activeUsers?.map((i) => ({
date: i.date,
users: i.count,
limit: 80,
}))}
/>
)}
{licenses && licenses.length > 0 && (
<>
<LicenseSeatConsumptionChart
limit={userLimitLimit}
data={activeUsers?.map((i) => ({
date: i.date,
users: i.count,
limit: 80,
}))}
/>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{hasUserLimitEntitlementData && (
<SeatUsageBarCard
title="Seat usage"
actual={userLimitActual}
limit={userLimitLimit}
allowUnlimited
/>
)}
<AIGovernanceUsersConsumption
aiGovernanceUserFeature={aiGovernanceUserFeature}
licenses={licenses}
/>
</div>
<ManagedAgentsConsumption
managedAgentFeature={managedAgentFeature}
/>
<AIGovernanceUsersConsumption
aiGovernanceUserFeature={aiGovernanceUserFeature}
/>
</>
)}
</div>
@@ -0,0 +1,64 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { expect, within } from "storybook/test";
import { SeatUsageBarCard } from "./SeatUsageBarCard";
const meta: Meta<typeof SeatUsageBarCard> = {
title: "pages/DeploymentSettingsPage/LicensesSettingsPage/SeatUsageBarCard",
component: SeatUsageBarCard,
args: {
title: "Seat usage",
actual: 1923,
limit: 2500,
},
};
export default meta;
type Story = StoryObj<typeof SeatUsageBarCard>;
export const Default: Story = {};
export const NearLimit: Story = {
args: {
actual: 2400,
limit: 2500,
},
};
export const OverLimit: Story = {
args: {
actual: 2600,
limit: 2500,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(canvas.getByText("2,600")).toBeInTheDocument();
await expect(canvas.getByText("2,500")).toBeInTheDocument();
},
};
export const MissingActual: Story = {
args: {
actual: undefined,
limit: 1000,
},
};
export const ErrorInvalidLimit: Story = {
args: {
actual: 100,
limit: undefined,
},
};
export const Unlimited: Story = {
args: {
actual: 1923,
limit: undefined,
allowUnlimited: true,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(canvas.getByText("1,923")).toBeInTheDocument();
await expect(canvas.getByText("Unlimited")).toBeInTheDocument();
},
};
@@ -0,0 +1,84 @@
import { ErrorAlert } from "components/Alert/ErrorAlert";
import type { FC } from "react";
import { cn } from "utils/cn";
type SeatUsageBarCardProps = {
title: string;
actual: number | undefined;
limit: number | undefined;
allowUnlimited?: boolean;
};
export const SeatUsageBarCard: FC<SeatUsageBarCardProps> = ({
title,
actual,
limit,
allowUnlimited = false,
}) => {
const isUnlimited = allowUnlimited && limit === undefined;
if (!isUnlimited && (limit === undefined || limit < 0)) {
return (
<section className="border border-solid rounded">
<div className="p-4">
<ErrorAlert error="Invalid license usage limits" />
</div>
</section>
);
}
const meteredLimit = limit ?? 0;
const activeNum = actual ?? 0;
const isExceeded =
!isUnlimited && actual !== undefined && actual > meteredLimit;
const usagePercentage = isUnlimited
? 100
: meteredLimit > 0
? Math.min((activeNum / meteredLimit) * 100, 100)
: 0;
const activeLabel =
actual === undefined ? "—" : activeNum.toLocaleString("en-US");
const limitLabel = isUnlimited
? "Unlimited"
: meteredLimit.toLocaleString("en-US");
return (
<section className={cn("border border-solid rounded")}>
<div className="p-4">
<div className="flex flex-col gap-2">
<h3 className="text-md m-0 font-medium">{title}</h3>
<div
className="relative h-5 w-full overflow-hidden rounded bg-surface-secondary"
aria-hidden="true"
>
<div
className={cn(
"h-full rounded-l transition-[width] duration-300",
isExceeded ? "bg-highlight-red" : "bg-highlight-green",
)}
style={{ width: `${usagePercentage}%` }}
/>
</div>
<div className="flex items-start justify-between text-sm font-medium whitespace-nowrap">
<p className="m-0 text-content-primary">
<span className="text-content-secondary">Active: </span>
<span
className={cn({
"text-content-destructive": isExceeded,
})}
>
{activeLabel}
</span>
</p>
<p className="m-0 text-content-secondary">
Limit: <span className="text-content-primary">{limitLabel}</span>
</p>
</div>
</div>
</div>
</section>
);
};