mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: link group names to group page in agents limit settings (#24212)
This commit is contained in:
@@ -415,6 +415,7 @@ export const AgentSettingsSpendPageView: FC<
|
||||
<GroupLimitsSection
|
||||
hideHeader
|
||||
groupOverrides={groupOverrides}
|
||||
groupOrganizationNames={groupCtrl.groupOrganizationNames}
|
||||
showGroupForm={groupCtrl.showGroupForm}
|
||||
onShowGroupFormChange={
|
||||
groupCtrl.handleShowGroupFormChange
|
||||
|
||||
@@ -72,6 +72,11 @@ const meta: Meta<typeof GroupLimitsSection> = {
|
||||
component: GroupLimitsSection,
|
||||
args: {
|
||||
groupOverrides: mockGroupOverrides,
|
||||
groupOrganizationNames: {
|
||||
"group-1": "acme",
|
||||
"group-2": "acme",
|
||||
"group-4": "acme",
|
||||
},
|
||||
showGroupForm: false,
|
||||
onShowGroupFormChange: fn(),
|
||||
selectedGroup: null,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Check } from "lucide-react";
|
||||
import { type FC, useId, useState } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { getErrorMessage } from "#/api/errors";
|
||||
import type { Group } from "#/api/typesGenerated";
|
||||
import type { ChatUsageLimitGroupOverride, Group } from "#/api/typesGenerated";
|
||||
import { Autocomplete } from "#/components/Autocomplete/Autocomplete";
|
||||
import { AvatarData } from "#/components/Avatar/AvatarData";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
@@ -26,14 +27,9 @@ import { SectionHeader } from "../SectionHeader";
|
||||
|
||||
interface GroupLimitsSectionProps {
|
||||
hideHeader?: boolean;
|
||||
groupOverrides: ReadonlyArray<{
|
||||
group_id: string;
|
||||
group_display_name: string;
|
||||
group_name: string;
|
||||
group_avatar_url: string;
|
||||
member_count: number;
|
||||
spend_limit_micros: number | null;
|
||||
}>;
|
||||
groupOverrides: readonly ChatUsageLimitGroupOverride[];
|
||||
/** Maps group_id → organization_name for building links to the group page. */
|
||||
groupOrganizationNames?: Record<string, string>;
|
||||
showGroupForm: boolean;
|
||||
onShowGroupFormChange: (show: boolean) => void;
|
||||
selectedGroup: Group | null;
|
||||
@@ -65,6 +61,7 @@ interface GroupLimitsSectionProps {
|
||||
export const GroupLimitsSection: FC<GroupLimitsSectionProps> = ({
|
||||
hideHeader,
|
||||
groupOverrides,
|
||||
groupOrganizationNames,
|
||||
showGroupForm,
|
||||
onShowGroupFormChange,
|
||||
selectedGroup,
|
||||
@@ -111,48 +108,68 @@ export const GroupLimitsSection: FC<GroupLimitsSectionProps> = ({
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{groupOverrides.map((override) => (
|
||||
<TableRow key={override.group_id}>
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
title={override.group_display_name || override.group_name}
|
||||
subtitle={override.group_name}
|
||||
src={override.group_avatar_url}
|
||||
imgFallbackText={override.group_name}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{override.member_count}</TableCell>
|
||||
<TableCell>
|
||||
{override.spend_limit_micros !== null
|
||||
? formatCostMicros(override.spend_limit_micros)
|
||||
: "Unlimited"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={() => onEditGroupOverride(override)}
|
||||
disabled={deletePending || upsertPending}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setPendingDeleteGroupId(override.group_id)
|
||||
}
|
||||
disabled={deletePending || upsertPending || isEditing}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{groupOverrides.map((override) => {
|
||||
const orgName = groupOrganizationNames?.[override.group_id];
|
||||
const groupLink = orgName
|
||||
? `/organizations/${orgName}/groups/${override.group_name}`
|
||||
: undefined;
|
||||
|
||||
const avatar = (
|
||||
<AvatarData
|
||||
title={override.group_display_name || override.group_name}
|
||||
subtitle={override.group_name}
|
||||
src={override.group_avatar_url}
|
||||
imgFallbackText={override.group_name}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow key={override.group_id}>
|
||||
<TableCell>
|
||||
{groupLink ? (
|
||||
<Link
|
||||
to={groupLink}
|
||||
className="flex rounded-md no-underline text-inherit -m-1.5 p-1.5 transition-colors hover:bg-surface-secondary"
|
||||
>
|
||||
{avatar}
|
||||
</Link>
|
||||
) : (
|
||||
avatar
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{override.member_count}</TableCell>
|
||||
<TableCell>
|
||||
{override.spend_limit_micros !== null
|
||||
? formatCostMicros(override.spend_limit_micros)
|
||||
: "Unlimited"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={() => onEditGroupOverride(override)}
|
||||
disabled={deletePending || upsertPending}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setPendingDeleteGroupId(override.group_id)
|
||||
}
|
||||
disabled={deletePending || upsertPending || isEditing}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
|
||||
@@ -37,6 +37,7 @@ type GroupOverrideChildProps = {
|
||||
existingGroupIds: Set<string>;
|
||||
availableGroups: Group[];
|
||||
groupAutocompleteNoOptionsText: string;
|
||||
groupOrganizationNames: Record<string, string>;
|
||||
};
|
||||
|
||||
interface GroupOverrideControllerProps {
|
||||
@@ -67,6 +68,10 @@ export const GroupOverrideController: FC<GroupOverrideControllerProps> = ({
|
||||
// Derived values.
|
||||
const existingGroupIds = new Set(groupOverrides.map((g) => g.group_id));
|
||||
const availableGroups = groups.filter((g) => !existingGroupIds.has(g.id));
|
||||
const groupOrganizationNames: Record<string, string> = {};
|
||||
for (const g of groups) {
|
||||
groupOrganizationNames[g.id] = g.organization_name;
|
||||
}
|
||||
const groupAutocompleteNoOptionsText = isLoadingGroups
|
||||
? "Loading groups..."
|
||||
: groups.length === 0
|
||||
@@ -137,5 +142,6 @@ export const GroupOverrideController: FC<GroupOverrideControllerProps> = ({
|
||||
existingGroupIds,
|
||||
availableGroups,
|
||||
groupAutocompleteNoOptionsText,
|
||||
groupOrganizationNames,
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user