feat: add code diff display mode preference (#25027)

This commit is contained in:
Danielle Maywood
2026-05-07 20:15:28 +01:00
committed by GitHub
parent d32842f084
commit e7958713a9
29 changed files with 968 additions and 207 deletions
+19
View File
@@ -14922,6 +14922,19 @@ const docTemplate = `{
}
}
},
"codersdk.AgentDisplayMode": {
"type": "string",
"enum": [
"auto",
"always_expanded",
"always_collapsed"
],
"x-enum-varnames": [
"AgentDisplayModeAuto",
"AgentDisplayModeAlwaysExpanded",
"AgentDisplayModeAlwaysCollapsed"
]
},
"codersdk.AgentScriptTiming": {
"type": "object",
"properties": {
@@ -23385,6 +23398,9 @@ const docTemplate = `{
"codersdk.UpdateUserPreferenceSettingsRequest": {
"type": "object",
"properties": {
"code_diff_display_mode": {
"$ref": "#/definitions/codersdk.AgentDisplayMode"
},
"task_notification_alert_dismissed": {
"type": "boolean"
},
@@ -23855,6 +23871,9 @@ const docTemplate = `{
"codersdk.UserPreferenceSettings": {
"type": "object",
"properties": {
"code_diff_display_mode": {
"$ref": "#/definitions/codersdk.AgentDisplayMode"
},
"task_notification_alert_dismissed": {
"type": "boolean"
},
+15
View File
@@ -13388,6 +13388,15 @@
}
}
},
"codersdk.AgentDisplayMode": {
"type": "string",
"enum": ["auto", "always_expanded", "always_collapsed"],
"x-enum-varnames": [
"AgentDisplayModeAuto",
"AgentDisplayModeAlwaysExpanded",
"AgentDisplayModeAlwaysCollapsed"
]
},
"codersdk.AgentScriptTiming": {
"type": "object",
"properties": {
@@ -21529,6 +21538,9 @@
"codersdk.UpdateUserPreferenceSettingsRequest": {
"type": "object",
"properties": {
"code_diff_display_mode": {
"$ref": "#/definitions/codersdk.AgentDisplayMode"
},
"task_notification_alert_dismissed": {
"type": "boolean"
},
@@ -21970,6 +21982,9 @@
"codersdk.UserPreferenceSettings": {
"type": "object",
"properties": {
"code_diff_display_mode": {
"$ref": "#/definitions/codersdk.AgentDisplayMode"
},
"task_notification_alert_dismissed": {
"type": "boolean"
},
+22
View File
@@ -4408,6 +4408,17 @@ func (q *querier) GetUserChatSpendInPeriod(ctx context.Context, arg database.Get
return q.db.GetUserChatSpendInPeriod(ctx, arg)
}
func (q *querier) GetUserCodeDiffDisplayMode(ctx context.Context, userID uuid.UUID) (string, error) {
user, err := q.db.GetUserByID(ctx, userID)
if err != nil {
return "", err
}
if err := q.authorizeContext(ctx, policy.ActionReadPersonal, user); err != nil {
return "", err
}
return q.db.GetUserCodeDiffDisplayMode(ctx, userID)
}
func (q *querier) GetUserCount(ctx context.Context, includeSystem bool) (int64, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
return 0, err
@@ -7024,6 +7035,17 @@ func (q *querier) UpdateUserChatProviderKey(ctx context.Context, arg database.Up
return q.db.UpdateUserChatProviderKey(ctx, arg)
}
func (q *querier) UpdateUserCodeDiffDisplayMode(ctx context.Context, arg database.UpdateUserCodeDiffDisplayModeParams) (string, error) {
user, err := q.db.GetUserByID(ctx, arg.UserID)
if err != nil {
return "", err
}
if err := q.authorizeContext(ctx, policy.ActionUpdatePersonal, user); err != nil {
return "", err
}
return q.db.UpdateUserCodeDiffDisplayMode(ctx, arg)
}
func (q *querier) UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error {
return deleteQ(q.log, q.auth, q.db.GetUserByID, q.db.UpdateUserDeletedByID)(ctx, id)
}
+13
View File
@@ -2829,6 +2829,19 @@ func (s *MethodTestSuite) TestUser() {
dbm.EXPECT().UpdateUserThinkingDisplayMode(gomock.Any(), arg).Return("always_expanded", nil).AnyTimes()
check.Args(arg).Asserts(u, policy.ActionUpdatePersonal).Returns("always_expanded")
}))
s.Run("GetUserCodeDiffDisplayMode", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
u := testutil.Fake(s.T(), faker, database.User{})
dbm.EXPECT().GetUserByID(gomock.Any(), u.ID).Return(u, nil).AnyTimes()
dbm.EXPECT().GetUserCodeDiffDisplayMode(gomock.Any(), u.ID).Return("auto", nil).AnyTimes()
check.Args(u.ID).Asserts(u, policy.ActionReadPersonal).Returns("auto")
}))
s.Run("UpdateUserCodeDiffDisplayMode", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
u := testutil.Fake(s.T(), faker, database.User{})
arg := database.UpdateUserCodeDiffDisplayModeParams{UserID: u.ID, CodeDiffDisplayMode: "always_collapsed"}
dbm.EXPECT().GetUserByID(gomock.Any(), u.ID).Return(u, nil).AnyTimes()
dbm.EXPECT().UpdateUserCodeDiffDisplayMode(gomock.Any(), arg).Return("always_collapsed", nil).AnyTimes()
check.Args(arg).Asserts(u, policy.ActionUpdatePersonal).Returns("always_collapsed")
}))
s.Run("ListUserChatCompactionThresholds", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
u := testutil.Fake(s.T(), faker, database.User{})
uc := database.UserConfig{UserID: u.ID, Key: codersdk.ChatCompactionThresholdKeyPrefix + "00000000-0000-0000-0000-000000000001", Value: "75"}
+16
View File
@@ -2848,6 +2848,14 @@ func (m queryMetricsStore) GetUserChatSpendInPeriod(ctx context.Context, arg dat
return r0, r1
}
func (m queryMetricsStore) GetUserCodeDiffDisplayMode(ctx context.Context, userID uuid.UUID) (string, error) {
start := time.Now()
r0, r1 := m.s.GetUserCodeDiffDisplayMode(ctx, userID)
m.queryLatencies.WithLabelValues("GetUserCodeDiffDisplayMode").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetUserCodeDiffDisplayMode").Inc()
return r0, r1
}
func (m queryMetricsStore) GetUserCount(ctx context.Context, includeSystem bool) (int64, error) {
start := time.Now()
r0, r1 := m.s.GetUserCount(ctx, includeSystem)
@@ -5008,6 +5016,14 @@ func (m queryMetricsStore) UpdateUserChatProviderKey(ctx context.Context, arg da
return r0, r1
}
func (m queryMetricsStore) UpdateUserCodeDiffDisplayMode(ctx context.Context, arg database.UpdateUserCodeDiffDisplayModeParams) (string, error) {
start := time.Now()
r0, r1 := m.s.UpdateUserCodeDiffDisplayMode(ctx, arg)
m.queryLatencies.WithLabelValues("UpdateUserCodeDiffDisplayMode").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateUserCodeDiffDisplayMode").Inc()
return r0, r1
}
func (m queryMetricsStore) UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error {
start := time.Now()
r0 := m.s.UpdateUserDeletedByID(ctx, id)
+30
View File
@@ -5327,6 +5327,21 @@ func (mr *MockStoreMockRecorder) GetUserChatSpendInPeriod(ctx, arg any) *gomock.
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserChatSpendInPeriod", reflect.TypeOf((*MockStore)(nil).GetUserChatSpendInPeriod), ctx, arg)
}
// GetUserCodeDiffDisplayMode mocks base method.
func (m *MockStore) GetUserCodeDiffDisplayMode(ctx context.Context, userID uuid.UUID) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserCodeDiffDisplayMode", ctx, userID)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserCodeDiffDisplayMode indicates an expected call of GetUserCodeDiffDisplayMode.
func (mr *MockStoreMockRecorder) GetUserCodeDiffDisplayMode(ctx, userID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserCodeDiffDisplayMode", reflect.TypeOf((*MockStore)(nil).GetUserCodeDiffDisplayMode), ctx, userID)
}
// GetUserCount mocks base method.
func (m *MockStore) GetUserCount(ctx context.Context, includeSystem bool) (int64, error) {
m.ctrl.T.Helper()
@@ -9441,6 +9456,21 @@ func (mr *MockStoreMockRecorder) UpdateUserChatProviderKey(ctx, arg any) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserChatProviderKey", reflect.TypeOf((*MockStore)(nil).UpdateUserChatProviderKey), ctx, arg)
}
// UpdateUserCodeDiffDisplayMode mocks base method.
func (m *MockStore) UpdateUserCodeDiffDisplayMode(ctx context.Context, arg database.UpdateUserCodeDiffDisplayModeParams) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUserCodeDiffDisplayMode", ctx, arg)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateUserCodeDiffDisplayMode indicates an expected call of UpdateUserCodeDiffDisplayMode.
func (mr *MockStoreMockRecorder) UpdateUserCodeDiffDisplayMode(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserCodeDiffDisplayMode", reflect.TypeOf((*MockStore)(nil).UpdateUserCodeDiffDisplayMode), ctx, arg)
}
// UpdateUserDeletedByID mocks base method.
func (m *MockStore) UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error {
m.ctrl.T.Helper()
+2
View File
@@ -709,6 +709,7 @@ type sqlcQuerier interface {
// returned (global behavior). Otherwise only spend within the
// specified organization is included.
GetUserChatSpendInPeriod(ctx context.Context, arg GetUserChatSpendInPeriodParams) (int64, error)
GetUserCodeDiffDisplayMode(ctx context.Context, userID uuid.UUID) (string, error)
GetUserCount(ctx context.Context, includeSystem bool) (int64, error)
// Returns the minimum (most restrictive) group limit for a user.
// Returns -1 if no group limits match the specified scope.
@@ -1193,6 +1194,7 @@ type sqlcQuerier interface {
UpdateUserChatCompactionThreshold(ctx context.Context, arg UpdateUserChatCompactionThresholdParams) (UserConfig, error)
UpdateUserChatCustomPrompt(ctx context.Context, arg UpdateUserChatCustomPromptParams) (UserConfig, error)
UpdateUserChatProviderKey(ctx context.Context, arg UpdateUserChatProviderKeyParams) (UserChatProviderKey, error)
UpdateUserCodeDiffDisplayMode(ctx context.Context, arg UpdateUserCodeDiffDisplayModeParams) (string, error)
UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error
UpdateUserGithubComUserID(ctx context.Context, arg UpdateUserGithubComUserIDParams) error
UpdateUserHashedOneTimePasscode(ctx context.Context, arg UpdateUserHashedOneTimePasscodeParams) error
+44
View File
@@ -25703,6 +25703,23 @@ func (q *sqlQuerier) GetUserChatPersonalModelOverride(ctx context.Context, arg G
return personal_model_override, err
}
const getUserCodeDiffDisplayMode = `-- name: GetUserCodeDiffDisplayMode :one
SELECT
value AS code_diff_display_mode
FROM
user_configs
WHERE
user_id = $1
AND key = 'preference_code_diff_display_mode'
`
func (q *sqlQuerier) GetUserCodeDiffDisplayMode(ctx context.Context, userID uuid.UUID) (string, error) {
row := q.db.QueryRowContext(ctx, getUserCodeDiffDisplayMode, userID)
var code_diff_display_mode string
err := row.Scan(&code_diff_display_mode)
return code_diff_display_mode, err
}
const getUserCount = `-- name: GetUserCount :one
SELECT
COUNT(*)
@@ -26301,6 +26318,33 @@ func (q *sqlQuerier) UpdateUserChatCustomPrompt(ctx context.Context, arg UpdateU
return i, err
}
const updateUserCodeDiffDisplayMode = `-- name: UpdateUserCodeDiffDisplayMode :one
INSERT INTO
user_configs (user_id, key, value)
VALUES
($1, 'preference_code_diff_display_mode', $2::text)
ON CONFLICT
ON CONSTRAINT user_configs_pkey
DO UPDATE
SET
value = $2
WHERE user_configs.user_id = $1
AND user_configs.key = 'preference_code_diff_display_mode'
RETURNING value AS code_diff_display_mode
`
type UpdateUserCodeDiffDisplayModeParams struct {
UserID uuid.UUID `db:"user_id" json:"user_id"`
CodeDiffDisplayMode string `db:"code_diff_display_mode" json:"code_diff_display_mode"`
}
func (q *sqlQuerier) UpdateUserCodeDiffDisplayMode(ctx context.Context, arg UpdateUserCodeDiffDisplayModeParams) (string, error) {
row := q.db.QueryRowContext(ctx, updateUserCodeDiffDisplayMode, arg.UserID, arg.CodeDiffDisplayMode)
var code_diff_display_mode string
err := row.Scan(&code_diff_display_mode)
return code_diff_display_mode, err
}
const updateUserDeletedByID = `-- name: UpdateUserDeletedByID :exec
UPDATE
users
+24
View File
@@ -303,6 +303,30 @@ WHERE user_configs.user_id = @user_id
AND user_configs.key = 'preference_thinking_display_mode'
RETURNING value AS thinking_display_mode;
-- name: GetUserCodeDiffDisplayMode :one
SELECT
value AS code_diff_display_mode
FROM
user_configs
WHERE
user_id = @user_id
AND key = 'preference_code_diff_display_mode';
-- name: UpdateUserCodeDiffDisplayMode :one
INSERT INTO
user_configs (user_id, key, value)
VALUES
(@user_id, 'preference_code_diff_display_mode', @code_diff_display_mode::text)
ON CONFLICT
ON CONSTRAINT user_configs_pkey
DO UPDATE
SET
value = @code_diff_display_mode
WHERE user_configs.user_id = @user_id
AND user_configs.key = 'preference_code_diff_display_mode'
RETURNING value AS code_diff_display_mode;
-- name: UpdateUserRoles :one
UPDATE
users
+120 -51
View File
@@ -1248,9 +1248,19 @@ func (api *API) userPreferenceSettings(rw http.ResponseWriter, r *http.Request)
return
}
codeDiffMode, err := api.Database.GetUserCodeDiffDisplayMode(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Error reading user preference settings.",
Detail: err.Error(),
})
return
}
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UserPreferenceSettings{
TaskNotificationAlertDismissed: taskAlertDismissed,
ThinkingDisplayMode: sanitizeThinkingDisplayMode(thinkingMode),
CodeDiffDisplayMode: sanitizeAgentDisplayMode(codeDiffMode),
})
}
@@ -1280,69 +1290,120 @@ func (api *API) putUserPreferenceSettings(rw http.ResponseWriter, r *http.Reques
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid thinking display mode.",
Validations: []codersdk.ValidationError{
{Field: "thinking_display_mode", Detail: "must be one of: auto, preview, always_expanded, always_collapsed"},
{Field: "thinking_display_mode", Detail: thinkingDisplayModeValidationDetail},
},
})
return
}
var err error
var updatedTaskAlertDismissed bool
if params.TaskNotificationAlertDismissed != nil {
updatedTaskAlertDismissed, err = api.Database.UpdateUserTaskNotificationAlertDismissed(ctx, database.UpdateUserTaskNotificationAlertDismissedParams{
UserID: user.ID,
TaskNotificationAlertDismissed: *params.TaskNotificationAlertDismissed,
if params.CodeDiffDisplayMode != "" &&
!slices.Contains(codersdk.ValidAgentDisplayModes, params.CodeDiffDisplayMode) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid code diff display mode.",
Validations: []codersdk.ValidationError{
{Field: "code_diff_display_mode", Detail: agentDisplayModeValidationDetail},
},
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating user task notification alert dismissed.",
Detail: err.Error(),
})
return
}
} else {
updatedTaskAlertDismissed, err = api.Database.GetUserTaskNotificationAlertDismissed(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Error reading task notification alert dismissed.",
Detail: err.Error(),
return
}
var settings codersdk.UserPreferenceSettings
err := api.Database.InTx(func(tx database.Store) error {
var err error
if params.TaskNotificationAlertDismissed != nil {
settings.TaskNotificationAlertDismissed, err = tx.UpdateUserTaskNotificationAlertDismissed(ctx, database.UpdateUserTaskNotificationAlertDismissedParams{
UserID: user.ID,
TaskNotificationAlertDismissed: *params.TaskNotificationAlertDismissed,
})
if err != nil {
return newUserPreferenceSettingsAPIError("Internal error updating user task notification alert dismissed.", err)
}
} else {
settings.TaskNotificationAlertDismissed, err = tx.GetUserTaskNotificationAlertDismissed(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return newUserPreferenceSettingsAPIError("Error reading task notification alert dismissed.", err)
}
}
if params.ThinkingDisplayMode != "" {
updated, err := tx.UpdateUserThinkingDisplayMode(ctx, database.UpdateUserThinkingDisplayModeParams{
UserID: user.ID,
ThinkingDisplayMode: string(params.ThinkingDisplayMode),
})
if err != nil {
return newUserPreferenceSettingsAPIError("Internal error updating thinking display mode.", err)
}
settings.ThinkingDisplayMode = sanitizeThinkingDisplayMode(updated)
} else {
stored, err := tx.GetUserThinkingDisplayMode(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return newUserPreferenceSettingsAPIError("Error reading thinking display mode.", err)
}
settings.ThinkingDisplayMode = sanitizeThinkingDisplayMode(stored)
}
if params.CodeDiffDisplayMode != "" {
updated, err := tx.UpdateUserCodeDiffDisplayMode(ctx, database.UpdateUserCodeDiffDisplayModeParams{
UserID: user.ID,
CodeDiffDisplayMode: string(params.CodeDiffDisplayMode),
})
if err != nil {
return newUserPreferenceSettingsAPIError("Internal error updating code diff display mode.", err)
}
settings.CodeDiffDisplayMode = sanitizeAgentDisplayMode(updated)
} else {
stored, err := tx.GetUserCodeDiffDisplayMode(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return newUserPreferenceSettingsAPIError("Error reading code diff display mode.", err)
}
settings.CodeDiffDisplayMode = sanitizeAgentDisplayMode(stored)
}
return nil
}, database.DefaultTXOptions().WithID("user_preference_settings"))
if err != nil {
var apiErr userPreferenceSettingsAPIError
if errors.As(err, &apiErr) {
httpapi.Write(ctx, rw, apiErr.statusCode, apiErr.response)
return
}
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating user preference settings.",
Detail: err.Error(),
})
return
}
var resolvedThinkingMode codersdk.ThinkingDisplayMode
if params.ThinkingDisplayMode != "" {
updated, err := api.Database.UpdateUserThinkingDisplayMode(ctx, database.UpdateUserThinkingDisplayModeParams{
UserID: user.ID,
ThinkingDisplayMode: string(params.ThinkingDisplayMode),
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating thinking display mode.",
Detail: err.Error(),
})
return
}
resolvedThinkingMode = codersdk.ThinkingDisplayMode(updated)
} else {
stored, err := api.Database.GetUserThinkingDisplayMode(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Error reading thinking display mode.",
Detail: err.Error(),
})
return
}
resolvedThinkingMode = sanitizeThinkingDisplayMode(stored)
}
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UserPreferenceSettings{
TaskNotificationAlertDismissed: updatedTaskAlertDismissed,
ThinkingDisplayMode: resolvedThinkingMode,
})
httpapi.Write(ctx, rw, http.StatusOK, settings)
}
type userPreferenceSettingsAPIError struct {
statusCode int
response codersdk.Response
err error
}
func newUserPreferenceSettingsAPIError(message string, err error) userPreferenceSettingsAPIError {
return userPreferenceSettingsAPIError{
statusCode: http.StatusInternalServerError,
response: codersdk.Response{
Message: message,
Detail: err.Error(),
},
err: err,
}
}
func (e userPreferenceSettingsAPIError) Error() string {
return fmt.Sprintf("%s: %s", e.response.Message, e.err)
}
func (e userPreferenceSettingsAPIError) Unwrap() error {
return e.err
}
const (
thinkingDisplayModeValidationDetail = "must be one of: auto, preview, always_expanded, always_collapsed"
agentDisplayModeValidationDetail = "must be one of: auto, always_expanded, always_collapsed"
)
func sanitizeThinkingDisplayMode(raw string) codersdk.ThinkingDisplayMode {
mode := codersdk.ThinkingDisplayMode(raw)
if slices.Contains(codersdk.ValidThinkingDisplayModes, mode) {
@@ -1351,6 +1412,14 @@ func sanitizeThinkingDisplayMode(raw string) codersdk.ThinkingDisplayMode {
return codersdk.ThinkingDisplayModeAuto
}
func sanitizeAgentDisplayMode(raw string) codersdk.AgentDisplayMode {
mode := codersdk.AgentDisplayMode(raw)
if slices.Contains(codersdk.ValidAgentDisplayModes, mode) {
return mode
}
return codersdk.AgentDisplayModeAuto
}
func isValidFontName(font codersdk.TerminalFontName) bool {
return slices.Contains(codersdk.TerminalFontNames, font)
}
+111
View File
@@ -1963,6 +1963,117 @@ func TestThinkingDisplayMode(t *testing.T) {
})
}
func TestAgentDisplayModePreferences(t *testing.T) {
t.Parallel()
adminClient := coderdtest.New(t, nil)
firstUser := coderdtest.CreateFirstUser(t, adminClient)
requireValidationField := func(t *testing.T, err error, field string) {
t.Helper()
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr)
require.Equal(t, http.StatusBadRequest, sdkErr.StatusCode())
require.Len(t, sdkErr.Validations, 1)
require.Equal(t, field, sdkErr.Validations[0].Field)
}
t.Run("defaults to auto", func(t *testing.T) {
t.Parallel()
client, _ := coderdtest.CreateAnotherUser(t, adminClient, firstUser.OrganizationID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
settings, err := client.GetUserPreferenceSettings(ctx, codersdk.Me)
require.NoError(t, err)
require.Equal(t, codersdk.AgentDisplayModeAuto, settings.CodeDiffDisplayMode)
})
t.Run("round-trips code diff display mode", func(t *testing.T) {
t.Parallel()
client, _ := coderdtest.CreateAnotherUser(t, adminClient, firstUser.OrganizationID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
for _, mode := range []codersdk.AgentDisplayMode{
codersdk.AgentDisplayModeAlwaysExpanded,
codersdk.AgentDisplayModeAlwaysCollapsed,
} {
updated, err := client.UpdateUserPreferenceSettings(ctx, codersdk.Me, codersdk.UpdateUserPreferenceSettingsRequest{
CodeDiffDisplayMode: mode,
})
require.NoError(t, err)
require.Equal(t, mode, updated.CodeDiffDisplayMode)
settings, err := client.GetUserPreferenceSettings(ctx, codersdk.Me)
require.NoError(t, err)
require.Equal(t, mode, settings.CodeDiffDisplayMode)
}
})
t.Run("updates preserve stored display modes", func(t *testing.T) {
t.Parallel()
client, _ := coderdtest.CreateAnotherUser(t, adminClient, firstUser.OrganizationID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
_, err := client.UpdateUserPreferenceSettings(ctx, codersdk.Me, codersdk.UpdateUserPreferenceSettingsRequest{
ThinkingDisplayMode: codersdk.ThinkingDisplayModePreview,
CodeDiffDisplayMode: codersdk.AgentDisplayModeAlwaysExpanded,
})
require.NoError(t, err)
updated, err := client.UpdateUserPreferenceSettings(ctx, codersdk.Me, codersdk.UpdateUserPreferenceSettingsRequest{
ThinkingDisplayMode: codersdk.ThinkingDisplayModeAlwaysExpanded,
})
require.NoError(t, err)
require.Equal(t, codersdk.ThinkingDisplayModeAlwaysExpanded, updated.ThinkingDisplayMode)
require.Equal(t, codersdk.AgentDisplayModeAlwaysExpanded, updated.CodeDiffDisplayMode)
settings, err := client.GetUserPreferenceSettings(ctx, codersdk.Me)
require.NoError(t, err)
require.Equal(t, codersdk.ThinkingDisplayModeAlwaysExpanded, settings.ThinkingDisplayMode)
require.Equal(t, codersdk.AgentDisplayModeAlwaysExpanded, settings.CodeDiffDisplayMode)
})
t.Run("rejects invalid code diff display mode", func(t *testing.T) {
t.Parallel()
client, _ := coderdtest.CreateAnotherUser(t, adminClient, firstUser.OrganizationID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
for _, tt := range []struct {
name string
mode codersdk.AgentDisplayMode
}{
{
name: "bogus",
mode: codersdk.AgentDisplayMode("bogus"),
},
{
name: "thinking preview",
mode: codersdk.AgentDisplayMode(codersdk.ThinkingDisplayModePreview),
},
} {
t.Run(tt.name, func(t *testing.T) {
_, err := client.UpdateUserPreferenceSettings(ctx, codersdk.Me, codersdk.UpdateUserPreferenceSettingsRequest{
CodeDiffDisplayMode: tt.mode,
})
requireValidationField(t, err, "code_diff_display_mode")
})
}
})
}
func TestWorkspacesByUser(t *testing.T) {
t.Parallel()
t.Run("Empty", func(t *testing.T) {