From 50504f8f64b6d5ad5cca4cad2b2a3d8f164c19e4 Mon Sep 17 00:00:00 2001 From: Susana Cardoso Ferreira Date: Tue, 28 Jul 2026 12:08:43 +0000 Subject: [PATCH 1/2] feat(site): label AI spend as estimated --- .../Navbar/UserDropdown/UserDropdown.stories.tsx | 16 ++++++++++------ .../Navbar/UserDropdown/UserDropdownAISpend.tsx | 2 +- site/src/pages/GroupsPage/GroupMembersPage.tsx | 12 +++++++++--- site/src/pages/GroupsPage/GroupPage.stories.tsx | 6 ++++-- site/src/pages/GroupsPage/GroupsPageView.tsx | 11 +++++++++-- site/src/pages/GroupsPage/StatusIconTooltip.tsx | 13 +++++++++++++ 6 files changed, 46 insertions(+), 14 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx index 6260b96dc71..6057f14d06d 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx @@ -73,7 +73,7 @@ export const WithAISpend: Story = { await waitFor(() => expect(document.body).toHaveTextContent("$819 / $1,200 USD"), ); - expect(document.body).toHaveTextContent("(AI spend/month)"); + expect(document.body).toHaveTextContent("(Estimated AI spend/month)"); expect( screen.getByRole("progressbar", { name: "AI spend usage" }), ).toHaveAttribute("aria-valuenow", "68"); @@ -97,7 +97,7 @@ export const AISpendWarning: Story = { await waitFor(() => expect(document.body).toHaveTextContent("$1,080 / $1,200 USD"), ); - expect(document.body).toHaveTextContent("(AI spend/month)"); + expect(document.body).toHaveTextContent("(Estimated AI spend/month)"); expect( screen.getByRole("progressbar", { name: "AI spend usage" }), ).toHaveAttribute("aria-valuenow", "90"); @@ -144,7 +144,7 @@ export const AISpendExceeded: Story = { await waitFor(() => expect(document.body).toHaveTextContent("$1,500 / $1,200 USD"), ); - expect(document.body).toHaveTextContent("(AI spend/month)"); + expect(document.body).toHaveTextContent("(Estimated AI spend/month)"); expect( screen.getByRole("progressbar", { name: "AI spend usage" }), ).toHaveAttribute("aria-valuenow", "100"); @@ -165,7 +165,7 @@ export const AISpendUnlimited: Story = { await waitFor(() => expect(document.body).toHaveTextContent("$819 / Unlimited USD"), ); - expect(document.body).toHaveTextContent("(AI spend/month)"); + expect(document.body).toHaveTextContent("(Estimated AI spend/month)"); expect( screen.queryByRole("progressbar", { name: "AI spend usage" }), ).not.toBeInTheDocument(); @@ -272,7 +272,9 @@ export const AISpendHiddenOnInvalidData: Story = { play: async ({ canvasElement, step }) => { await step("hides AI spend on invalid data", async () => { await openDropdown(canvasElement); - expect(screen.queryByText("(AI spend/month)")).not.toBeInTheDocument(); + expect( + screen.queryByText("(Estimated AI spend/month)"), + ).not.toBeInTheDocument(); }); }, }; @@ -293,7 +295,9 @@ export const AISpendHiddenOnNegativeLimit: Story = { play: async ({ canvasElement, step }) => { await step("hides AI spend on a negative limit", async () => { await openDropdown(canvasElement); - expect(screen.queryByText("(AI spend/month)")).not.toBeInTheDocument(); + expect( + screen.queryByText("(Estimated AI spend/month)"), + ).not.toBeInTheDocument(); }); }, }; diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownAISpend.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownAISpend.tsx index 2cff36afa45..17e9816b7bc 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownAISpend.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownAISpend.tsx @@ -31,7 +31,7 @@ export const UserDropdownAISpend: FC = ({ /> )}
- (AI spend/month) + (Estimated AI spend/month)
); diff --git a/site/src/pages/GroupsPage/GroupMembersPage.tsx b/site/src/pages/GroupsPage/GroupMembersPage.tsx index 953a87c7cd6..ae7805b1852 100644 --- a/site/src/pages/GroupsPage/GroupMembersPage.tsx +++ b/site/src/pages/GroupsPage/GroupMembersPage.tsx @@ -57,7 +57,7 @@ import { GroupMemberBudgetCells, } from "./GroupMemberBudgetCells"; import type { GroupPageOutletContext } from "./GroupPage"; -import { StatusIconTooltip } from "./StatusIconTooltip"; +import { SpendEstimateDocsLink, StatusIconTooltip } from "./StatusIconTooltip"; import { UserAIBudgetOverrideDialog } from "./UserAIBudgetOverrideDialog"; type MemberWithSpend = ReducedUser & { @@ -112,7 +112,7 @@ const GroupMembersPage: FC = () => { }), ); const aiBudgetNote = [ - "Monthly AI spend for this user.", + "Estimated monthly AI spend for this user.", // Spend resets at period_end, rendered in the viewer's local time. aiSpend && `Resets ${dayjs(aiSpend.period_end).format("MMM D, YYYY h:mm A")}.`, @@ -173,7 +173,13 @@ const GroupMembersPage: FC = () => { message="AI spend couldn't be loaded, so budgets aren't shown." /> ) : ( - + + {aiBudgetNote} + + } + /> )} diff --git a/site/src/pages/GroupsPage/GroupPage.stories.tsx b/site/src/pages/GroupsPage/GroupPage.stories.tsx index 2a42dd04b16..c8a5e62231c 100644 --- a/site/src/pages/GroupsPage/GroupPage.stories.tsx +++ b/site/src/pages/GroupsPage/GroupPage.stories.tsx @@ -365,7 +365,7 @@ export const WithMemberAIBudget: Story = { ); await expect( await body.findByText( - /^Monthly AI spend for this user\. Resets .*The group's default limit is \$7,000 per member\.$/, + /^Estimated monthly AI spend for this user\. Resets .*The group's default limit is \$7,000 per member\.$/, ), ).toBeInTheDocument(); await userEvent.click( @@ -434,7 +434,9 @@ export const AIBudgetActionDisabledForOtherGroup: Story = { }), ); await expect( - await body.findByText(/^Monthly AI spend for this user\. Resets .*\.$/), + await body.findByText( + /^Estimated monthly AI spend for this user\. Resets .*\.$/, + ), ).toBeInTheDocument(); await userEvent.keyboard("{Escape}"); diff --git a/site/src/pages/GroupsPage/GroupsPageView.tsx b/site/src/pages/GroupsPage/GroupsPageView.tsx index f7ed9fffd1d..53110bccda2 100644 --- a/site/src/pages/GroupsPage/GroupsPageView.tsx +++ b/site/src/pages/GroupsPage/GroupsPageView.tsx @@ -25,7 +25,7 @@ import { } from "#/components/TableLoader/TableLoader"; import { useClickableTableRow } from "#/hooks/useClickableTableRow"; import { docs } from "#/utils/docs"; -import { StatusIconTooltip } from "./StatusIconTooltip"; +import { SpendEstimateDocsLink, StatusIconTooltip } from "./StatusIconTooltip"; const EM_DASH = "\u2014"; @@ -94,7 +94,14 @@ export const GroupsPageView: FC = ({ message="AI spend couldn't be loaded, so budgets aren't shown." /> ) : ( - + + Estimated AI spend compared to the group's AI budget for + the active period. + + } + /> )} diff --git a/site/src/pages/GroupsPage/StatusIconTooltip.tsx b/site/src/pages/GroupsPage/StatusIconTooltip.tsx index 4613aead3ef..d01b33f23a4 100644 --- a/site/src/pages/GroupsPage/StatusIconTooltip.tsx +++ b/site/src/pages/GroupsPage/StatusIconTooltip.tsx @@ -6,6 +6,8 @@ import { HelpPopoverIconTrigger, HelpPopoverText, } from "#/components/HelpPopover/HelpPopover"; +import { Link } from "#/components/Link/Link"; +import { docs } from "#/utils/docs"; type StatusIconKind = "info" | "warning"; @@ -28,3 +30,14 @@ export const StatusIconTooltip: FC<{ ); + +/** Links to the AI Governance cost controls docs. */ +export const SpendEstimateDocsLink: FC = () => ( + + How spend is estimated + +); From 30674dfb33f7ad15d33906f2b33f22f69f158d12 Mon Sep 17 00:00:00 2001 From: Susana Cardoso Ferreira Date: Mon, 3 Aug 2026 13:46:09 +0000 Subject: [PATCH 2/2] chore: address review comments --- .../Navbar/UserDropdown/UserDropdown.stories.tsx | 8 ++------ site/src/pages/GroupsPage/AICostControl.tsx | 13 +++++++++++++ site/src/pages/GroupsPage/GroupMembersPage.tsx | 3 ++- site/src/pages/GroupsPage/GroupsPageView.tsx | 3 ++- site/src/pages/GroupsPage/StatusIconTooltip.tsx | 13 ------------- 5 files changed, 19 insertions(+), 21 deletions(-) create mode 100644 site/src/pages/GroupsPage/AICostControl.tsx diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx index 6057f14d06d..ff48b732541 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx @@ -272,9 +272,7 @@ export const AISpendHiddenOnInvalidData: Story = { play: async ({ canvasElement, step }) => { await step("hides AI spend on invalid data", async () => { await openDropdown(canvasElement); - expect( - screen.queryByText("(Estimated AI spend/month)"), - ).not.toBeInTheDocument(); + expect(screen.queryByText(/spend\/month/)).not.toBeInTheDocument(); }); }, }; @@ -295,9 +293,7 @@ export const AISpendHiddenOnNegativeLimit: Story = { play: async ({ canvasElement, step }) => { await step("hides AI spend on a negative limit", async () => { await openDropdown(canvasElement); - expect( - screen.queryByText("(Estimated AI spend/month)"), - ).not.toBeInTheDocument(); + expect(screen.queryByText(/spend\/month/)).not.toBeInTheDocument(); }); }, }; diff --git a/site/src/pages/GroupsPage/AICostControl.tsx b/site/src/pages/GroupsPage/AICostControl.tsx new file mode 100644 index 00000000000..31f43623ae4 --- /dev/null +++ b/site/src/pages/GroupsPage/AICostControl.tsx @@ -0,0 +1,13 @@ +import type { FC } from "react"; +import { Link } from "#/components/Link/Link"; +import { docs } from "#/utils/docs"; + +export const SpendEstimateDocsLink: FC = () => ( + + How spend is estimated + +); diff --git a/site/src/pages/GroupsPage/GroupMembersPage.tsx b/site/src/pages/GroupsPage/GroupMembersPage.tsx index ae7805b1852..fa8c1d7808b 100644 --- a/site/src/pages/GroupsPage/GroupMembersPage.tsx +++ b/site/src/pages/GroupsPage/GroupMembersPage.tsx @@ -52,12 +52,13 @@ import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility"; import { isEveryoneGroup } from "#/modules/groups"; import { cn } from "#/utils/cn"; import { formatBudgetUSD } from "#/utils/currency"; +import { SpendEstimateDocsLink } from "./AICostControl"; import { effectiveBudgetGroup, GroupMemberBudgetCells, } from "./GroupMemberBudgetCells"; import type { GroupPageOutletContext } from "./GroupPage"; -import { SpendEstimateDocsLink, StatusIconTooltip } from "./StatusIconTooltip"; +import { StatusIconTooltip } from "./StatusIconTooltip"; import { UserAIBudgetOverrideDialog } from "./UserAIBudgetOverrideDialog"; type MemberWithSpend = ReducedUser & { diff --git a/site/src/pages/GroupsPage/GroupsPageView.tsx b/site/src/pages/GroupsPage/GroupsPageView.tsx index 53110bccda2..79e44597904 100644 --- a/site/src/pages/GroupsPage/GroupsPageView.tsx +++ b/site/src/pages/GroupsPage/GroupsPageView.tsx @@ -25,7 +25,8 @@ import { } from "#/components/TableLoader/TableLoader"; import { useClickableTableRow } from "#/hooks/useClickableTableRow"; import { docs } from "#/utils/docs"; -import { SpendEstimateDocsLink, StatusIconTooltip } from "./StatusIconTooltip"; +import { SpendEstimateDocsLink } from "./AICostControl"; +import { StatusIconTooltip } from "./StatusIconTooltip"; const EM_DASH = "\u2014"; diff --git a/site/src/pages/GroupsPage/StatusIconTooltip.tsx b/site/src/pages/GroupsPage/StatusIconTooltip.tsx index d01b33f23a4..4613aead3ef 100644 --- a/site/src/pages/GroupsPage/StatusIconTooltip.tsx +++ b/site/src/pages/GroupsPage/StatusIconTooltip.tsx @@ -6,8 +6,6 @@ import { HelpPopoverIconTrigger, HelpPopoverText, } from "#/components/HelpPopover/HelpPopover"; -import { Link } from "#/components/Link/Link"; -import { docs } from "#/utils/docs"; type StatusIconKind = "info" | "warning"; @@ -30,14 +28,3 @@ export const StatusIconTooltip: FC<{ ); - -/** Links to the AI Governance cost controls docs. */ -export const SpendEstimateDocsLink: FC = () => ( - - How spend is estimated - -);