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
15 changes: 3 additions & 12 deletions site/src/pages/GroupsPage/GroupMemberBudgetCells.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,29 +287,20 @@ export const ResolvingGroupName: Story = {
},
};

/** An effective group that can't be resolved, standing in for another org's. */
export const NotAttributedUnknownGroup: Story = {
export const NotAttributedOtherOrganization: Story = {
args: {
spend: {
...mockSpend,
group_spend_micros: 456_000_000,
effective_group_id: "external-group",
effective_group_id: null,
group_budget: null,
},
},
parameters: {
queries: [
{
key: getGroupByIdQueryKey("external-group", { exclude_members: true }),
data: null,
},
],
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const cell = await canvas.findByTestId(testId);
await expect(cell).toHaveTextContent("\u2014");
await expect(cell).not.toHaveTextContent("$456");
// The group cell shows an em-dash + info instead of naming the group.
const groupCell = canvas.getAllByRole("cell")[1];
await expect(groupCell).toHaveTextContent("\u2014");
await userEvent.click(
Expand Down
8 changes: 5 additions & 3 deletions site/src/pages/GroupsPage/GroupMemberBudgetCells.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ describe("effectiveBudgetGroup", () => {
expect(effectiveBudgetGroup(undefined, group)).toEqual({ kind: "none" });
});

it("is other without a governing group (budget in another org)", () => {
expect(effectiveBudgetGroup(mockSpend, group)).toEqual({ kind: "other" });
it("is other organization when the effective group is masked", () => {
expect(effectiveBudgetGroup(mockSpend, group)).toEqual({
kind: "otherOrg",
});
});

it("is everyone for the org-wide Everyone group", () => {
Expand Down Expand Up @@ -53,6 +55,6 @@ describe("effectiveBudgetGroup", () => {
{ ...mockSpend, effective_group_id: "group-2" },
group,
),
).toEqual({ kind: "other" });
).toEqual({ kind: "otherGroup" });
});
});
46 changes: 26 additions & 20 deletions site/src/pages/GroupsPage/GroupMemberBudgetCells.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const GroupMemberBudgetCells: FC<{
spend: GroupMemberAISpend | undefined;
}> = ({ group, userID, spend }) => {
const effective = effectiveBudgetGroup(spend, group);
const fromOtherGroup = effective.kind === "other";
const fromOtherGroup = effective.kind === "otherGroup";

// A null effective_group_id is a group in another org that can't be
// fetched, so only resolve the name when an ID exists.
Expand Down Expand Up @@ -64,30 +64,32 @@ export const GroupMemberBudgetCells: FC<{
case "this":
budgetGroup = <Badge size="sm">{badgeName(groupName)}</Badge>;
break;
case "other": {
case "otherGroup":
// Wait for the name to resolve rather than flashing the fallback.
if (isResolvingGroupName) {
budgetGroup = <Spinner loading size="sm" />;
} else if (effectiveGroupName) {
budgetGroup = <Badge size="sm">{badgeName(effectiveGroupName)}</Badge>;
} else {
// The group can't be resolved (another org), so it can't be named.
budgetGroup = (
<LabelWithInfo label={EM_DASH} message={OTHER_ORG_MESSAGE} />
budgetGroup = effectiveGroupName ? (
<Badge size="sm">{badgeName(effectiveGroupName)}</Badge>
) : (
EM_DASH
);
}
break;
}
case "otherOrg":
budgetGroup = (
<LabelWithInfo label={EM_DASH} message={OTHER_ORG_MESSAGE} />
);
break;
}

let budget: ReactNode = EM_DASH;
if (spend && fromOtherGroup) {
if (spend && effective.kind === "otherOrg") {
budget = <LabelWithInfo label={EM_DASH} message={OTHER_ORG_MESSAGE} />;
} else if (spend && effective.kind === "otherGroup") {
if (isResolvingGroupName) {
budget = <Spinner loading size="sm" />;
} else if (!effectiveGroupName) {
// The spend hides entirely when the governing group can't be resolved.
budget = <LabelWithInfo label={EM_DASH} message={OTHER_ORG_MESSAGE} />;
} else {
} else if (effectiveGroupName) {
budget = (
<div className="flex flex-col gap-0.5">
<span className="flex items-center gap-1">
Expand Down Expand Up @@ -171,21 +173,25 @@ type EffectiveBudgetGroup =
| { kind: "none" }
| { kind: "everyone" }
| { kind: "this" }
| { kind: "other" };
| { kind: "otherGroup" }
| { kind: "otherOrg" };

/**
* Resolves which group governs a member's AI budget. "none" means no budget
* data loaded; "everyone" is the org-wide fallback when no named group sets a
* budget. A null effective group means the budget resolves to a group in
* another organization, so it can't be shown here.
* Resolves which group governs a member's AI budget:
*
* - "none": spend data is not loaded.
* - "everyone": the Everyone group governs the budget.
* - "this": the viewed group governs the budget.
* - "otherGroup": another group in this organization governs the budget.
* - "otherOrg": a group in another organization governs the budget.
*/
export function effectiveBudgetGroup(
spend: GroupMemberAISpend | undefined,
group: Pick<Group, "id" | "organization_id">,
): EffectiveBudgetGroup {
const groupId = spend?.effective_group_id ?? null;
if (groupId === null) {
return spend === undefined ? { kind: "none" } : { kind: "other" };
return spend === undefined ? { kind: "none" } : { kind: "otherOrg" };
}
// Everyone shares the org's id; checked first so it wins when the viewed
// group is Everyone itself.
Expand All @@ -195,7 +201,7 @@ export function effectiveBudgetGroup(
if (groupId === group.id) {
return { kind: "this" };
}
return { kind: "other" };
return { kind: "otherGroup" };
}

const LabelWithInfo: FC<{ label: ReactNode; message: ReactNode }> = ({
Expand Down
6 changes: 3 additions & 3 deletions site/src/pages/GroupsPage/GroupMembersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ const GroupMemberRow: FC<GroupMemberRowProps> = ({
onManageAIBudget,
onRemove,
}) => {
const budgetFromOtherGroup =
effectiveBudgetGroup(member.spend, group).kind === "other";
const budgetFromOtherOrganization =
effectiveBudgetGroup(member.spend, group).kind === "otherOrg";

return (
<TableRow key={member.id}>
Expand Down Expand Up @@ -288,7 +288,7 @@ const GroupMemberRow: FC<GroupMemberRowProps> = ({
{showAIBudget && (
<DropdownMenuItem
onClick={onManageAIBudget}
disabled={budgetFromOtherGroup}
disabled={budgetFromOtherOrganization}
Comment on lines 290 to +291

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So now we can manage other groups within the same org?

Ah tests do say that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We always supported changing a user's budget from another group. In fact, we also support changing it from another org, as long as the user belongs to the target group and the caller can update both the user and that group. However, in the UI we want to keep org-scope concerns visible, and showing this information depending on these conditions would be more complex. However, since most cases will happen in the context of the same org, I think this is a nice addition 🙂

>
Manage AI budget
</DropdownMenuItem>
Expand Down
10 changes: 5 additions & 5 deletions site/src/pages/GroupsPage/GroupPage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ export const WithoutMemberAIBudgetColumn: Story = {
},
};

export const AIBudgetActionDisabledForOtherGroup: Story = {
export const AIBudgetActionEnabledForOtherGroup: Story = {
parameters: {
features: ["aibridge"],
queries: [
Expand Down Expand Up @@ -489,7 +489,7 @@ export const AIBudgetActionDisabledForOtherGroup: Story = {
const menuItem = await body.findByRole("menuitem", {
name: "Manage AI budget",
});
await expect(menuItem).toHaveAttribute("aria-disabled", "true");
await expect(menuItem).not.toHaveAttribute("aria-disabled", "true");
},
};

Expand Down Expand Up @@ -929,14 +929,14 @@ export const AIBudgetShowcase: Story = {
await expect(manageItem).not.toHaveAttribute("aria-disabled", "true");
await userEvent.keyboard("{Escape}");

// Another named group does disable it.
// Another group in the same organization keeps the action enabled.
const otherGroupMenu = await canvas.findAllByRole("button", {
name: "Open menu",
});
await userEvent.click(otherGroupMenu[7]);
const disabledItem = await body.findByRole("menuitem", {
const otherGroupItem = await body.findByRole("menuitem", {
name: "Manage AI budget",
});
await expect(disabledItem).toHaveAttribute("aria-disabled", "true");
await expect(otherGroupItem).not.toHaveAttribute("aria-disabled", "true");
},
};
Loading