mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add code diff display mode preference (#25027)
This commit is contained in:
Generated
+19
@@ -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"
|
||||
},
|
||||
|
||||
Generated
+15
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user