feat: add inline editing of usage limit overrides (#23380)

## Summary

Adds inline editing of existing per-user and per-group chat usage limit
overrides from the Limits tab. Admins can now click Edit on any override
row to modify the spend limit in-place, using the same form used for
adding overrides.

## Changes

**Backend** (`coderd/chats_test.go`)
- Added `UpdateUserOverride` and `UpdateGroupOverride` test cases
  covering the upsert-in-place behavior.

**Frontend** (3 component files + 2 story files)
- `LimitsTab.tsx`: Edit state management, mutual-exclusion between
  user/group edit modes, and handlers that prefill the form from the
  existing override.
- `GroupLimitsSection.tsx`: Edit button per row, read-only group
  identity in edit mode, Save/Cancel actions, disable states during
  pending operations.
- `UserOverridesSection.tsx`: Same pattern as groups — Edit button,
  read-only user identity, Save/Cancel, proper disable states.
- New Storybook stories for both sections (Default, EmptyState,
  AddForm, EditForm).

## UX behavior

- Clicking Edit opens the inline form with the current spend limit
  prefilled and the entity shown as read-only.
- Save uses the existing PUT upsert endpoint (no new API surface).
- Cancel returns to normal list view with form state cleared.
- Edit modes are mutually exclusive — editing a user override closes
  any open group form and vice versa.
- All buttons and inputs disable during pending mutations.
- Add and delete continue to work after editing.
This commit is contained in:
Michael Suchacz
2026-03-20 22:28:32 +01:00
committed by GitHub
parent 24ab216dd1
commit b8a5344c92
6 changed files with 566 additions and 73 deletions
+63
View File
@@ -3716,6 +3716,35 @@ func TestChatUsageLimitOverrideRoutes(t *testing.T) {
require.Equal(t, "Chat usage limit override not found.", sdkErr.Message)
})
t.Run("UpdateUserOverride", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, _ := newChatClientWithDatabase(t)
firstUser := coderdtest.CreateFirstUser(t, client)
_, member := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID)
_, err := client.UpsertChatUsageLimitOverride(ctx, member.ID, codersdk.UpsertChatUsageLimitOverrideRequest{
SpendLimitMicros: 5_000_000,
})
require.NoError(t, err)
override, err := client.UpsertChatUsageLimitOverride(ctx, member.ID, codersdk.UpsertChatUsageLimitOverrideRequest{
SpendLimitMicros: 10_000_000,
})
require.NoError(t, err)
require.Equal(t, member.ID, override.UserID)
require.NotNil(t, override.SpendLimitMicros)
require.EqualValues(t, 10_000_000, *override.SpendLimitMicros)
config, err := client.GetChatUsageLimitConfig(ctx)
require.NoError(t, err)
require.Len(t, config.Overrides, 1)
require.Equal(t, member.ID, config.Overrides[0].UserID)
require.NotNil(t, config.Overrides[0].SpendLimitMicros)
require.EqualValues(t, 10_000_000, *config.Overrides[0].SpendLimitMicros)
})
t.Run("UpsertGroupOverrideIncludesMemberCount", func(t *testing.T) {
t.Parallel()
@@ -3750,6 +3779,40 @@ func TestChatUsageLimitOverrideRoutes(t *testing.T) {
require.EqualValues(t, 1, listed.MemberCount)
})
t.Run("UpdateGroupOverride", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, db := newChatClientWithDatabase(t)
firstUser := coderdtest.CreateFirstUser(t, client)
_, member := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID)
group := dbgen.Group(t, db, database.Group{OrganizationID: firstUser.OrganizationID})
dbgen.GroupMember(t, db, database.GroupMemberTable{GroupID: group.ID, UserID: firstUser.UserID})
dbgen.GroupMember(t, db, database.GroupMemberTable{GroupID: group.ID, UserID: member.ID})
_, err := client.UpsertChatUsageLimitGroupOverride(ctx, group.ID, codersdk.UpsertChatUsageLimitGroupOverrideRequest{
SpendLimitMicros: 5_000_000,
})
require.NoError(t, err)
override, err := client.UpsertChatUsageLimitGroupOverride(ctx, group.ID, codersdk.UpsertChatUsageLimitGroupOverrideRequest{
SpendLimitMicros: 10_000_000,
})
require.NoError(t, err)
require.Equal(t, group.ID, override.GroupID)
require.EqualValues(t, 2, override.MemberCount)
require.NotNil(t, override.SpendLimitMicros)
require.EqualValues(t, 10_000_000, *override.SpendLimitMicros)
config, err := client.GetChatUsageLimitConfig(ctx)
require.NoError(t, err)
require.Len(t, config.GroupOverrides, 1)
require.Equal(t, group.ID, config.GroupOverrides[0].GroupID)
require.EqualValues(t, 2, config.GroupOverrides[0].MemberCount)
require.NotNil(t, config.GroupOverrides[0].SpendLimitMicros)
require.EqualValues(t, 10_000_000, *config.GroupOverrides[0].SpendLimitMicros)
})
t.Run("UpsertGroupOverrideMissingGroup", func(t *testing.T) {
t.Parallel()