Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions site/src/components/AIBudgetUsage/AIBudgetUsage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,11 @@ export const ZeroBudget: Story = {
await expect(canvasElement).toHaveTextContent("$5 / $0 USD");
},
};

// Zero budget with no spend: shown as-is in the normal color.
export const ZeroBudgetNoSpend: Story = {
args: { currentSpend: 0, spendLimit: 0 },
play: async ({ canvasElement }) => {
await expect(canvasElement).toHaveTextContent("$0 / $0 USD");
},
};
97 changes: 82 additions & 15 deletions site/src/pages/GroupsPage/GroupMemberBudgetCells.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import {
TableHeader,
TableRow,
} from "#/components/Table/Table";
import { MockGroup2, MockGroupWithoutMembers } from "#/testHelpers/entities";
import {
MockEveryoneGroup,
MockGroup2,
MockGroupWithoutMembers,
} from "#/testHelpers/entities";
import { GroupMemberBudgetCells } from "./GroupMemberBudgetCells";

const group = MockGroupWithoutMembers;
Expand Down Expand Up @@ -74,14 +78,15 @@ export const Unlimited: Story = {
args: {
spend: {
...mockSpend,
group_spend_micros: 1_250_000_000,
group_budget: null,
effective_group_id: group.organization_id,
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(await canvas.findByTestId(testId)).toHaveTextContent(
"Unlimited",
"$1,250 / Unlimited USD",
);
await expect(
canvas.getByText("Everyone (not allocated)"),
Expand All @@ -91,38 +96,100 @@ export const Unlimited: Story = {
},
};

// Unlimited where the viewed group itself is the effective group.
export const UnlimitedThisGroup: Story = {
// Only the Everyone group can be an effective group without a budget.
export const UnlimitedEveryoneGroup: Story = {
args: {
group: MockEveryoneGroup,
spend: {
...mockSpend,
group_budget: null,
effective_group_id: MockEveryoneGroup.id,
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(await canvas.findByTestId(testId)).toHaveTextContent(
"Unlimited",
"$0 / Unlimited USD",
);
await expect(
canvas.getByText("Everyone (not allocated)"),
).toBeInTheDocument();
},
};

// The Everyone group's own budget governs; the badge drops "(not allocated)".
export const EveryoneGroupWithBudget: Story = {
args: {
group: MockEveryoneGroup,
spend: {
...mockSpend,
group_spend_micros: 1_250_000_000,
effective_group_id: MockEveryoneGroup.id,
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const cell = await canvas.findByTestId(testId);
await expect(cell).toHaveTextContent("$1,250 USD");
await expect(cell).toHaveTextContent("Group limit $7,000");
await expect(canvas.getByText("Everyone")).toBeInTheDocument();
await expect(canvas.queryByText(/not allocated/)).not.toBeInTheDocument();
},
};

// A user override resolving to the Everyone group shows as individual.
export const EveryoneGroupIndividual: Story = {
args: {
group: MockEveryoneGroup,
spend: {
...mockSpend,
group_spend_micros: 1_250_000_000,
effective_group_id: MockEveryoneGroup.id,
group_budget: {
spend_limit_micros: 9_000_000_000,
limit_source: "user_override",
},
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const cell = await canvas.findByTestId(testId);
await expect(cell).toHaveTextContent("Custom limit $9,000");
await expect(canvas.getByText("Everyone (individual)")).toBeInTheDocument();
},
};

// A $0 budget renders like any other limit; no spend keeps the normal color.
export const ZeroBudget: Story = {
Comment thread
EhabY marked this conversation as resolved.
args: {
spend: {
...mockSpend,
group_budget: { spend_limit_micros: 0, limit_source: "group" },
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const cell = await canvas.findByTestId(testId);
await expect(cell).toHaveTextContent("$0 USD");
await expect(cell).toHaveTextContent("Group limit $0");
await expect(canvas.getByText("Front-End")).toBeInTheDocument();
},
};

export const None: Story = {
// Visual variant of ZeroBudget: spend over a $0 budget takes the exceeded color.
export const ZeroBudgetExceeded: Story = {
args: {
spend: {
...mockSpend,
group_spend_micros: 100_000_000,
group_budget: { spend_limit_micros: 0, limit_source: "group" },
effective_group_id: group.organization_id,
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(await canvas.findByTestId(testId)).toHaveTextContent("None");
const body = await openInfo(canvasElement);
await expect(
await body.findByText(/no AI spending allowance/),
).toBeInTheDocument();
const cell = await canvas.findByTestId(testId);
await expect(cell).toHaveTextContent("$100 USD");
await expect(cell).toHaveTextContent("Group limit $0");
},
};

Expand Down Expand Up @@ -188,13 +255,13 @@ export const NotAttributed: Story = {
const canvas = within(canvasElement);
const cell = await canvas.findByTestId(testId);
await expect(cell).toHaveTextContent("$456 USD");
await expect(cell).toHaveTextContent("Not attributed to this group");
await expect(cell).toHaveTextContent("Budget managed by another group");
await expect(await canvas.findByText("developer")).toBeInTheDocument();
const body = await openInfo(canvasElement);
await expect(
await body.findByText(/None of this user's spend counts against/),
await body.findByText(/this user's spend in the/),
).toHaveTextContent(
"None of this user's spend counts against the Front-End group. It is managed by the developer group.",
"The amount shown is this user's spend in the Front-End group. Their AI budget is currently managed by the developer group.",
);
},
};
Expand Down
36 changes: 21 additions & 15 deletions site/src/pages/GroupsPage/GroupMemberBudgetCells.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useQuery } from "react-query";
import { groupById } from "#/api/queries/groups";
import type { Group, GroupMemberAISpend } from "#/api/typesGenerated";
import { AIBudgetAmount } from "#/components/AIBudgetAmount/AIBudgetAmount";
import { AIBudgetUsage } from "#/components/AIBudgetUsage/AIBudgetUsage";
import { Badge } from "#/components/Badge/Badge";
import { Spinner } from "#/components/Spinner/Spinner";
import { TableCell } from "#/components/Table/Table";
Expand All @@ -16,8 +17,8 @@ const OTHER_ORG_MESSAGE =
"This user's AI budget is managed by a group in another organization and isn't visible here.";

/**
* The AI budget and Budget group cells for a group member. Spend only counts
* against the viewed group; another group's budget shows as unattributed.
* The AI budget and Budget group cells for a group member. Spend is scoped to
* the viewed group; the limit comes from the member's effective group.
*/
export const GroupMemberBudgetCells: FC<{
group: Group;
Expand Down Expand Up @@ -50,7 +51,15 @@ export const GroupMemberBudgetCells: FC<{
budgetGroup = EM_DASH;
break;
case "everyone":
budgetGroup = <Badge size="sm">Everyone (not allocated)</Badge>;
// A populated budget means the Everyone group's own budget applies,
// so it isn't the unallocated fallback.
budgetGroup = (
<Badge size="sm">
{spend?.group_budget
? badgeName("Everyone")
: "Everyone (not allocated)"}
</Badge>
);
break;
case "this":
budgetGroup = <Badge size="sm">{badgeName(groupName)}</Badge>;
Expand Down Expand Up @@ -91,11 +100,11 @@ export const GroupMemberBudgetCells: FC<{
<StatusIconTooltip
message={
<>
None of this user's spend counts against the{" "}
The amount shown is this user's spend in the{" "}
<span className="font-medium text-content-primary">
{groupName}
</span>{" "}
group. It is managed by the{" "}
group. Their AI budget is currently managed by the{" "}
<span className="font-medium text-content-primary">
{effectiveGroupName}
</span>{" "}
Expand All @@ -105,7 +114,7 @@ export const GroupMemberBudgetCells: FC<{
/>
</span>
<span className="text-xs text-content-secondary">
Not attributed to this group
Budget managed by another group
</span>
</div>
);
Expand All @@ -116,18 +125,15 @@ export const GroupMemberBudgetCells: FC<{
// The effective group has no budget, so no limit applies.
budget = (
<LabelWithInfo
label="Unlimited"
label={
<AIBudgetUsage
currentSpend={spend.group_spend_micros}
spendLimit={null}
/>
}
message="None of this user's groups have an AI budget configured, so their AI usage isn't restricted."
/>
);
} else if (limit === 0) {
// A $0 budget disables spending, distinct from no budget configured.
budget = (
<LabelWithInfo
label="None"
message="This user's group(s) have an AI budget of $0, so they have no AI spending allowance."
/>
);
} else {
const limitLabel =
spend.group_budget?.limit_source === "user_override"
Expand Down
2 changes: 1 addition & 1 deletion site/src/testHelpers/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3243,7 +3243,7 @@ export const MockGroup3: TypesGen.Group = {
total_member_count: 2,
};

const MockEveryoneGroup: TypesGen.Group = {
export const MockEveryoneGroup: TypesGen.Group = {
// The "Everyone" group must have the same ID as a the organization it belongs
// to.
id: MockOrganization.id,
Expand Down
Loading