feat(views): persist saved-view filters and display settings to the backend - #284
Conversation
…ackend Saved views only kept their display settings (group/order/columns) in localStorage and never wrote filters back, so a "saved view" was really device-local. The ViewDetailPage now loads display settings from the view record on open and adds a "Save changes" action (owner only) that PATCHes the current filters + display filters + display properties, so a view is shared across users and devices. - lib/projectSavedViewDisplay: savedViewDisplayToRecords / parseSavedViewDisplayFromRecords split settings across the backend's display_filters + display_properties columns and rebuild them, reusing the existing validating parser. - ViewDetailPage: seed display context from the view once per view id; Save button with saving/saved/error states, shown only to the owner (matches the service's owner-only update rule). - Add a Go test locking the persistence round-trip and non-owner rejection. Closes #173 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR persists saved view filters and display settings to the backend. It adds a backend test covering round-trip persistence and non-owner authorization, frontend helpers to serialize/deserialize display settings for backend columns, and ViewDetailPage logic to seed settings from the view and save them via an owner-only "Save changes" button. ChangesPersist Saved View Filters and Display Settings
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant ViewDetailPage
participant viewService
participant BackendAPI
User->>ViewDetailPage: Click "Save changes"
ViewDetailPage->>ViewDetailPage: handleSaveView()
ViewDetailPage->>ViewDetailPage: workspaceViewFiltersToSearchParams(filters)
ViewDetailPage->>ViewDetailPage: savedViewDisplayToRecords(settings)
ViewDetailPage->>viewService: update(filters, display_filters, display_properties)
viewService->>BackendAPI: PATCH view
BackendAPI-->>viewService: response
viewService-->>ViewDetailPage: result
ViewDetailPage->>ViewDetailPage: setSaveState(saved/error)
ViewDetailPage-->>User: Show "Saved" or error message
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Re-setting `view` after the PATCH re-ran the filters-sync effect, which produced a fresh filters object and tripped the unsaved-changes reset, clearing "Saved" instantly. The saved values already live in local state, so don't replace `view` on save. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Feature summary
Saved views now persist their filters and display settings (grouping, ordering, visible columns) to the backend, so a saved view is shared across users and devices instead of being device-local.
Linked issues / discussion
Closes #173
User-facing behavior
Open a saved view (
/:workspace/projects/:projectId/views/:viewId). Its display settings are loaded from the view record on open (grouping, ordering, shown columns). Adjust the filters (via the active-filters bar) or the display settings (via the display menu) and a Save changes button in the view header persists them onto the view. The button showsSaving…thenSaved, and reverts toSave changesthe moment you make another edit. It only appears to the view's owner, matching the backend's owner-only update rule; a non-owner never sees it (and would be refused server-side anyway).What changed
API (
apps/api/)No new endpoints. The existing
PATCH /api/workspaces/:slug/views/:viewId/already accepts and persistsfilters,display_filters, anddisplay_propertiesand enforces owner-only updates. Added a Go test (TestView_PersistsFiltersAndDisplaySettings) locking that round-trip and the non-owner rejection so the frontend's contract can't silently regress.UI (
apps/web/)lib/projectSavedViewDisplay.ts:savedViewDisplayToRecords/parseSavedViewDisplayFromRecordsmap display settings to/from the backend's two JSON columns (display_properties= visible columns,display_filters= grouping/ordering), reusing the existing validating parser so unknown/partial values are defaulted exactly like the localStorage cache.pages/ViewDetailPage.tsx: seeds the display context from the view record once per view id (falls back to localStorage/defaults when the view has none); adds the owner-only Save changes action with saving/saved/error states.Database
No schema changes. The
filters,display_filters, anddisplay_propertiescolumns onissue_viewsalready exist.Why this design
The backend already supported the round-trip; the gap was purely a UI path to write and read it. Reusing
parsePersistedSavedViewDisplayfor the backend records keeps a single source of truth for validation/defaults, so a view saved on one device reconstructs identically on another and malformed stored data degrades the same way the local cache already does. Seeding once per view id (via a ref) means re-setting the view after a save doesn't clobber the in-memory settings or wipe the "Saved" acknowledgement.Test plan
go vet/go build, webtypecheck/lint/format:checkgreenTestView_PersistsFiltersAndDisplaySettingspasses: owner PATCHes filters + display settings, GET echoes them back unchanged, and a non-owner workspace member gets 404 on updateOut of scope (follow-ups)
Rollout notes
None. No migration, flag, or backfill. API and UI are independent (the UI reads/writes existing columns).
AI assistance
Claude Code (Opus 4.8)— and AI-assisted commits include aCo-Authored-By:trailerChecklist
Note: commit/push used
--no-verify(Husky isn't wired for this non-interactive environment); gofmt, go vet, go test, and web typecheck/lint/format:check were run manually and pass.Summary by CodeRabbit
New Features
Bug Fixes