Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
26 changes: 25 additions & 1 deletion apps/backend/internal/backendapp/boot_state_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ func (b bootStateBuilder) tasksPageBootData(ctx context.Context, req *http.Reque
settingsWorkspaceID := ""
settingsWorkflowID := ""
settingsRepositoryID := ""
settingsTasksListSort := ""
settingsTasksListGroup := ""
if hasSettings {
settingsWorkspaceID = settings.Settings.WorkspaceID
settingsWorkflowID = settings.Settings.WorkflowFilterID
settingsTasksListSort = settings.Settings.TasksListSort
settingsTasksListGroup = settings.Settings.TasksListGroup
if len(settings.Settings.RepositoryIDs) > 0 {
settingsRepositoryID = settings.Settings.RepositoryIDs[0]
}
}
tasksListSort := tasksListSortForRoute(queryValue(req, "sort"), settingsTasksListSort)
tasksListGroup := tasksListGroupForRoute(queryValue(req, "group"), settingsTasksListGroup)
workspaceIDs := workspaceIDSet(workspaces)
activeWorkspaceID := firstValidID(
workspaceIDs,
Expand All @@ -54,7 +60,7 @@ func (b bootStateBuilder) tasksPageBootData(ctx context.Context, req *http.Reque
state["userSettings"] = mapUserSettingsState(settings, activeWorkspaceID)
}
if activeWorkspaceID == "" {
return state, map[string]any{"activeWorkspaceId": nil, "workflows": []any{}, "steps": []any{}, "repositories": []any{}, "tasks": []any{}, "total": 0}
return state, map[string]any{"activeWorkspaceId": nil, "workflows": []any{}, "steps": []any{}, "repositories": []any{}, "tasks": []any{}, "total": 0, "tasksListSort": tasksListSort, "tasksListGroup": tasksListGroup}
}
workflows, err := b.p.taskSvc.ListWorkflows(ctx, activeWorkspaceID, false)
if err != nil {
Expand All @@ -77,6 +83,8 @@ func (b bootStateBuilder) tasksPageBootData(ctx context.Context, req *http.Reque
"repositories": repositories,
"tasks": tasks,
"total": total,
"tasksListSort": tasksListSort,
"tasksListGroup": tasksListGroup,
}
return state, routeData
}
Expand Down Expand Up @@ -438,6 +446,8 @@ func mapUserSettingsState(response userdto.UserSettingsResponse, workspaceID str
"kanbanViewMode": nullString(settings.KanbanViewMode),
"workflowId": nullString(settings.WorkflowFilterID),
"repositoryIds": stringSlice(settings.RepositoryIDs),
"tasksListSort": usermodels.NormalizeTasksListSort(settings.TasksListSort),
"tasksListGroup": usermodels.NormalizeTasksListGroup(settings.TasksListGroup),
"preferredShell": nullString(settings.PreferredShell),
"shellOptions": response.ShellOptions,
"defaultEditorId": nullString(settings.DefaultEditorID),
Expand Down Expand Up @@ -635,6 +645,20 @@ func queryValue(req *http.Request, name string) string {
return strings.TrimSpace(parsed.Query().Get(name))
}

func tasksListSortForRoute(queryValue, settingsValue string) string {
if usermodels.IsValidTasksListSort(queryValue) {
return strings.TrimSpace(queryValue)
}
return usermodels.NormalizeTasksListSort(settingsValue)
}

func tasksListGroupForRoute(queryValue, settingsValue string) string {
if usermodels.IsValidTasksListGroup(queryValue) {
return strings.TrimSpace(queryValue)
}
return usermodels.NormalizeTasksListGroup(settingsValue)
}

func readActiveWorkspaceCookie(req *http.Request) string {
if req == nil {
return ""
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/internal/user/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ func (c *Controller) UpdateUserSettings(ctx context.Context, req dto.UpdateUserS
KanbanViewMode: req.KanbanViewMode,
WorkflowFilterID: req.WorkflowFilterID,
RepositoryIDs: req.RepositoryIDs,
TasksListSort: req.TasksListSort,
TasksListGroup: req.TasksListGroup,
InitialSetupComplete: req.InitialSetupComplete,
PreferredShell: req.PreferredShell,
DefaultEditorID: req.DefaultEditorID,
Expand Down
6 changes: 6 additions & 0 deletions apps/backend/internal/user/dto/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type UserSettingsDTO struct {
KanbanViewMode string `json:"kanban_view_mode"`
WorkflowFilterID string `json:"workflow_filter_id"`
RepositoryIDs []string `json:"repository_ids"`
TasksListSort string `json:"tasks_list_sort"`
TasksListGroup string `json:"tasks_list_group"`
InitialSetupComplete bool `json:"initial_setup_complete"`
PreferredShell string `json:"preferred_shell"`
DefaultEditorID string `json:"default_editor_id"`
Expand Down Expand Up @@ -74,6 +76,8 @@ type UpdateUserSettingsRequest struct {
KanbanViewMode *string `json:"kanban_view_mode,omitempty"`
WorkflowFilterID *string `json:"workflow_filter_id,omitempty"`
RepositoryIDs *[]string `json:"repository_ids,omitempty"`
TasksListSort *string `json:"tasks_list_sort,omitempty"`
TasksListGroup *string `json:"tasks_list_group,omitempty"`
InitialSetupComplete *bool `json:"initial_setup_complete,omitempty"`
PreferredShell *string `json:"preferred_shell,omitempty"`
DefaultEditorID *string `json:"default_editor_id,omitempty"`
Expand Down Expand Up @@ -184,6 +188,8 @@ func FromUserSettings(settings *models.UserSettings) UserSettingsDTO {
KanbanViewMode: settings.KanbanViewMode,
WorkflowFilterID: settings.WorkflowFilterID,
RepositoryIDs: settings.RepositoryIDs,
TasksListSort: settings.TasksListSort,
TasksListGroup: settings.TasksListGroup,
InitialSetupComplete: settings.InitialSetupComplete,
PreferredShell: settings.PreferredShell,
DefaultEditorID: settings.DefaultEditorID,
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/internal/user/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ type UserSettings struct {
KanbanViewMode string `json:"kanban_view_mode"`
WorkflowFilterID string `json:"workflow_filter_id"`
RepositoryIDs []string `json:"repository_ids"`
TasksListSort string `json:"tasks_list_sort"`
TasksListGroup string `json:"tasks_list_group"`
InitialSetupComplete bool `json:"initial_setup_complete"`
PreferredShell string `json:"preferred_shell"`
DefaultEditorID string `json:"default_editor_id"`
Expand Down
48 changes: 48 additions & 0 deletions apps/backend/internal/user/models/tasks_list_preferences.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package models

import (
"slices"
"strings"
)

const (
TasksListSortDefault = "updated_desc"
TasksListGroupDefault = "state"
)

var (
tasksListSortValues = []string{TasksListSortDefault, "updated_asc", "created_desc", "created_asc", "title_asc", "title_desc"}
tasksListGroupValues = []string{TasksListGroupDefault, "workflow", "repository", "none"}
)

func TasksListSortValues() []string {
return append([]string(nil), tasksListSortValues...)
}

func TasksListGroupValues() []string {
return append([]string(nil), tasksListGroupValues...)
}

func IsValidTasksListSort(value string) bool {
return slices.Contains(tasksListSortValues, strings.TrimSpace(value))
}

func IsValidTasksListGroup(value string) bool {
return slices.Contains(tasksListGroupValues, strings.TrimSpace(value))
}

func NormalizeTasksListSort(value string) string {
value = strings.TrimSpace(value)
if IsValidTasksListSort(value) {
return value
}
return TasksListSortDefault
}

func NormalizeTasksListGroup(value string) string {
value = strings.TrimSpace(value)
if IsValidTasksListGroup(value) {
return value
}
return TasksListGroupDefault
}
31 changes: 31 additions & 0 deletions apps/backend/internal/user/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type UpdateUserSettingsRequest struct {
KanbanViewMode *string
WorkflowFilterID *string
RepositoryIDs *[]string
TasksListSort *string
TasksListGroup *string
InitialSetupComplete *bool
PreferredShell *string
DefaultEditorID *string
Expand Down Expand Up @@ -193,6 +195,9 @@ func applyBasicSettings(settings *models.UserSettings, req *UpdateUserSettingsRe
if req.RepositoryIDs != nil {
settings.RepositoryIDs = *req.RepositoryIDs
}
if err := applyTasksListPreferences(settings, req.TasksListSort, req.TasksListGroup); err != nil {
return err
}
if req.InitialSetupComplete != nil {
settings.InitialSetupComplete = *req.InitialSetupComplete
}
Expand Down Expand Up @@ -248,6 +253,30 @@ func applyBasicSettings(settings *models.UserSettings, req *UpdateUserSettingsRe
return nil
}

func applyTasksListPreferences(settings *models.UserSettings, sortValue, groupValue *string) error {
if sortValue != nil {
v := strings.TrimSpace(*sortValue)
if v == "" {
v = models.TasksListSortDefault
}
if !models.IsValidTasksListSort(v) {
return fmt.Errorf("tasks_list_sort must be one of %s", strings.Join(models.TasksListSortValues(), ", "))
}
settings.TasksListSort = v
}
if groupValue != nil {
v := strings.TrimSpace(*groupValue)
if v == "" {
v = models.TasksListGroupDefault
}
if !models.IsValidTasksListGroup(v) {
return fmt.Errorf("tasks_list_group must be one of %s", strings.Join(models.TasksListGroupValues(), ", "))
}
settings.TasksListGroup = v
}
return nil
}

func applyTerminalLinkBehavior(settings *models.UserSettings, value *string) error {
if value == nil {
return nil
Expand Down Expand Up @@ -505,6 +534,8 @@ func (s *Service) publishUserSettingsEvent(ctx context.Context, settings *models
"kanban_view_mode": settings.KanbanViewMode,
"workflow_filter_id": settings.WorkflowFilterID,
"repository_ids": settings.RepositoryIDs,
"tasks_list_sort": settings.TasksListSort,
"tasks_list_group": settings.TasksListGroup,
"initial_setup_complete": settings.InitialSetupComplete,
"preferred_shell": settings.PreferredShell,
"default_editor_id": settings.DefaultEditorID,
Expand Down
35 changes: 35 additions & 0 deletions apps/backend/internal/user/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,41 @@ func TestApplyBasicSettings_ReleaseNotes(t *testing.T) {
})
}

func TestApplyBasicSettings_TasksListPreferences(t *testing.T) {
t.Run("sets valid sort and group", func(t *testing.T) {
settings := &models.UserSettings{}
req := &UpdateUserSettingsRequest{
TasksListSort: ptr("title_asc"),
TasksListGroup: ptr("repository"),
}
if err := applyBasicSettings(settings, req); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if settings.TasksListSort != "title_asc" {
t.Fatalf("TasksListSort = %q, want title_asc", settings.TasksListSort)
}
if settings.TasksListGroup != "repository" {
t.Fatalf("TasksListGroup = %q, want repository", settings.TasksListGroup)
}
})

t.Run("rejects invalid sort", func(t *testing.T) {
settings := &models.UserSettings{}
req := &UpdateUserSettingsRequest{TasksListSort: ptr("priority_desc")}
if err := applyBasicSettings(settings, req); err == nil {
t.Fatal("expected invalid sort error")
}
})

t.Run("rejects invalid group", func(t *testing.T) {
settings := &models.UserSettings{}
req := &UpdateUserSettingsRequest{TasksListGroup: ptr("assignee")}
if err := applyBasicSettings(settings, req); err == nil {
t.Fatal("expected invalid group error")
}
})
}

func TestApplyBasicSettings_TerminalFontFamily(t *testing.T) {
t.Run("nil leaves settings unchanged", func(t *testing.T) {
settings := &models.UserSettings{TerminalFontFamily: "Fira Code"}
Expand Down
8 changes: 8 additions & 0 deletions apps/backend/internal/user/store/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ func marshalUserSettingsPayload(settings *models.UserSettings) ([]byte, error) {
"kanban_view_mode": settings.KanbanViewMode,
"workflow_filter_id": settings.WorkflowFilterID,
"repository_ids": settings.RepositoryIDs,
"tasks_list_sort": models.NormalizeTasksListSort(settings.TasksListSort),
"tasks_list_group": models.NormalizeTasksListGroup(settings.TasksListGroup),
"initial_setup_complete": settings.InitialSetupComplete,
"preferred_shell": settings.PreferredShell,
"default_editor_id": settings.DefaultEditorID,
Expand Down Expand Up @@ -447,6 +449,8 @@ func scanUserSettings(scanner interface{ Scan(dest ...any) error }, userID strin
settings.UserID = userID
if settingsRaw == "" || settingsRaw == "{}" {
settings.RepositoryIDs = []string{}
settings.TasksListSort = models.TasksListSortDefault
settings.TasksListGroup = models.TasksListGroupDefault
settings.ShowReleaseNotification = true
settings.ReviewAutoMarkOnScroll = true
settings.ChatSubmitKey = "cmd_enter"
Expand All @@ -463,6 +467,8 @@ func scanUserSettings(scanner interface{ Scan(dest ...any) error }, userID strin
KanbanViewMode string `json:"kanban_view_mode"`
WorkflowFilterID string `json:"workflow_filter_id"`
RepositoryIDs []string `json:"repository_ids"`
TasksListSort string `json:"tasks_list_sort"`
TasksListGroup string `json:"tasks_list_group"`
InitialSetupComplete bool `json:"initial_setup_complete"`
PreferredShell string `json:"preferred_shell"`
DefaultEditorID string `json:"default_editor_id"`
Expand Down Expand Up @@ -502,6 +508,8 @@ func scanUserSettings(scanner interface{ Scan(dest ...any) error }, userID strin
settings.KanbanViewMode = payload.KanbanViewMode
settings.WorkflowFilterID = payload.WorkflowFilterID
settings.RepositoryIDs = payload.RepositoryIDs
settings.TasksListSort = models.NormalizeTasksListSort(payload.TasksListSort)
settings.TasksListGroup = models.NormalizeTasksListGroup(payload.TasksListGroup)
settings.InitialSetupComplete = payload.InitialSetupComplete
settings.PreferredShell = payload.PreferredShell
settings.DefaultEditorID = payload.DefaultEditorID
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/pkg/api/v1/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type UserSettings struct {
KanbanViewMode string `json:"kanban_view_mode"`
WorkflowFilterID string `json:"workflow_filter_id"`
RepositoryIDs []string `json:"repository_ids"`
TasksListSort string `json:"tasks_list_sort"`
TasksListGroup string `json:"tasks_list_group"`
PreferredShell string `json:"preferred_shell"`
DefaultEditorID string `json:"default_editor_id"`
UpdatedAt time.Time `json:"updated_at"`
Expand Down
Loading
Loading