feat(saved-views): add organization and private saved views across API and UI#2160
feat(saved-views): add organization and private saved views across API and UI#2160
Conversation
There was a problem hiding this comment.
Pull request overview
Adds end-to-end Saved Views support (organization-wide + user-private) across the .NET API, Elasticsearch persistence, generated OpenAPI/TS client artifacts, and Svelte UI integrations for Events/Issues/Stream dashboards, including feature-flag gating at the organization level.
Changes:
- Introduces
SavedViewdomain model + repository/index, plus API endpoints for CRUD and listing by org/view with default-view semantics. - Adds mapping (Mapperly) + backend integration tests + OpenAPI snapshot updates for saved views and org features.
- Integrates Saved Views into the Svelte app (picker, auto-load default, sidebar submenu, org “Features” admin page), plus filter (de)serialization helpers and tests.
Reviewed changes
Copilot reviewed 40 out of 42 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/http/saved-views.http | Adds HTTP scratchpad requests for login, feature enablement, and saved-views CRUD flows. |
| tests/Exceptionless.Tests/Mapping/SavedViewMapperTests.cs | Validates Mapperly mappings between saved-view DTOs and domain/view models. |
| tests/Exceptionless.Tests/Controllers/SavedViewControllerTests.cs | Adds integration coverage for scoping, feature gating, validation, default behavior, and access control. |
| tests/Exceptionless.Tests/Controllers/OrganizationControllerTests.cs | Adds tests for enabling/disabling feature flags and returning features in org DTOs. |
| tests/Exceptionless.Tests/Controllers/Data/openapi.json | Updates OpenAPI snapshot to include saved-views endpoints and schemas. |
| src/Exceptionless.Web/Models/SavedView/ViewSavedView.cs | Adds API response DTO for saved views. |
| src/Exceptionless.Web/Models/SavedView/UpdateSavedView.cs | Adds PATCH/PUT DTO with validation hooks. |
| src/Exceptionless.Web/Models/SavedView/NewSavedView.cs | Adds POST DTO with server-side validation (view + filter defs + columns). |
| src/Exceptionless.Web/Models/Organization/ViewOrganization.cs | Adds Features collection to org view model for UI gating. |
| src/Exceptionless.Web/Mapping/SavedViewMapper.cs | Adds Mapperly mapper for SavedView DTO ↔ domain ↔ view model. |
| src/Exceptionless.Web/Mapping/ApiMapper.cs | Wires SavedView mapping methods into the central ApiMapper. |
| src/Exceptionless.Web/Controllers/SavedViewController.cs | Adds saved-views API endpoints + permissions + feature gating + default clearing logic. |
| src/Exceptionless.Web/Controllers/OrganizationController.cs | Adds global-admin feature-flag toggle endpoints; removes user private saved views on membership removal. |
| src/Exceptionless.Web/ClientApp/src/routes/routes.svelte.ts | Extends navigation item types to support saved-view children/default metadata. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte | Integrates saved view query param + picker into Stream dashboard. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/routes.svelte.ts | Adds “Features” nav item for global admins. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/features/+page.svelte | Adds UI for toggling known org feature flags. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte | Integrates saved view query param + picker into Issues dashboard. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte | Integrates saved view query param + picker into Events dashboard. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte | Adds websocket invalidation and sidebar submenu building from saved views. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar.svelte | Renders dashboard submenu for saved views and active highlighting logic. |
| src/Exceptionless.Web/ClientApp/src/lib/generated/schemas.ts | Adds generated Zod schemas for SavedView DTOs + org features. |
| src/Exceptionless.Web/ClientApp/src/lib/generated/api.ts | Adds generated TS interfaces for SavedView DTOs + org features. |
| src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/use-saved-views.svelte.ts | Implements saved-view loading, hydration, default auto-load, and modified detection. |
| src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/models.ts | Adds local TS models for saved-view requests/responses. |
| src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/index.ts | Exposes saved-view slice API/models for feature consumption. |
| src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/components/saved-view-picker.svelte | Adds picker UI for create/select/update/rename/delete/default operations with optimistic caching. |
| src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/api.svelte.ts | Adds query/mutation helpers + websocket-driven invalidation for saved views. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts | Adds client mutations for org feature enable/disable. |
| src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/helpers.svelte.ts | Adds filter serialization/deserialization used by saved views. |
| src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/helpers.svelte.test.ts | Adds Vitest coverage for filter (de)serialization round-trips and defensive parsing. |
| src/Exceptionless.Core/Services/OrganizationService.cs | Adds saved-view cleanup on org deletion and user membership removal. |
| src/Exceptionless.Core/Repositories/SavedViewRepository.cs | Adds ES queries for view/org scoping and private cleanup. |
| src/Exceptionless.Core/Repositories/Interfaces/ISavedViewRepository.cs | Defines saved-view repository contract. |
| src/Exceptionless.Core/Repositories/Configuration/Indexes/SavedViewIndex.cs | Adds ES index mapping/settings for saved views. |
| src/Exceptionless.Core/Repositories/Configuration/Indexes/OrganizationIndex.cs | Indexes org Features as keyword array. |
| src/Exceptionless.Core/Repositories/Configuration/ExceptionlessElasticConfiguration.cs | Registers the SavedViews index with the ES configuration. |
| src/Exceptionless.Core/Models/SavedView.cs | Adds core SavedView domain model + valid views/columns definitions. |
| src/Exceptionless.Core/Models/Organization.cs | Adds Features set + OrganizationFeatures constants. |
| src/Exceptionless.Core/Bootstrapper.cs | Registers ISavedViewRepository in DI. |
| .agents/skills/typescript-conventions/SKILL.md | Captures/relocates frontend code-style rules into owned skills docs. |
| .agents/skills/frontend-architecture/SKILL.md | Captures/relocates frontend architecture guidance into owned skills docs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [HttpPatch("{id:objectid}")] | ||
| [HttpPut("{id:objectid}")] | ||
| [Consumes("application/json")] | ||
| public Task<ActionResult<ViewSavedView>> PatchAsync(string id, Delta<UpdateSavedView> changes) | ||
| { |
| import { isEntityChangedType, type WebSocketMessageType } from '$features/websockets/models'; | ||
| import { WebSocketClient } from '$features/websockets/web-socket-client.svelte'; | ||
| import { useMiddleware } from '@exceptionless/fetchclient'; | ||
| import { useFetchClient, useMiddleware } from '@exceptionless/fetchclient'; |
| return new TagFilter(data.value as [] | undefined); | ||
| case 'type': | ||
| return new TypeFilter(data.value as [] | undefined); |
| }, | ||
| "columns": { | ||
| "type": "array" | ||
| }, |
| case 'level': | ||
| return new LevelFilter(data.value as [] | undefined); | ||
| case 'number': | ||
| return new NumberFilter(data.term, data.value as number | undefined); | ||
| case 'project': | ||
| return new ProjectFilter(data.value as string[] | undefined); |
| case 'status': | ||
| return new StatusFilter(data.value as [] | undefined); | ||
| case 'string': | ||
| return new StringFilter(data.term, data.value as string | undefined); |
f8028c4 to
67884ad
Compare
…I and UI Implements saved views end-to-end with repository/index support, API endpoints, and Svelte integration for events/issues/stream dashboards. Adds coverage for controller behavior and Mapperly mappings, including organization-wide vs private visibility and default-view behavior.
67884ad to
1056033
Compare
There was a problem hiding this comment.
Pull request overview
Adds end-to-end Saved Views support (org-wide and user-private) across the Exceptionless API and Svelte UI, including persistence/indexing, mappings, and tests, plus an admin-only organization feature toggle UI.
Changes:
- Introduces SavedView domain model + Elasticsearch index/repository, API controller endpoints, and Mapperly mappings.
- Adds organization feature-flag storage and admin endpoints/UI to toggle
feature-saved-views. - Integrates Saved Views selection/creation into Events/Issues/Stream dashboards and updates generated OpenAPI/client artifacts.
Reviewed changes
Copilot reviewed 40 out of 42 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/http/saved-views.http | Adds HTTP scratch workflow for feature toggle + CRUD of saved views. |
| tests/Exceptionless.Tests/Mapping/SavedViewMapperTests.cs | Verifies Mapperly mappings for SavedView DTOs. |
| tests/Exceptionless.Tests/Controllers/SavedViewControllerTests.cs | Integration tests for saved-views CRUD, scoping, defaults, validation, and feature gating. |
| tests/Exceptionless.Tests/Controllers/OrganizationControllerTests.cs | Adds tests for org feature enable/disable endpoints and DTO features field. |
| tests/Exceptionless.Tests/Controllers/Data/openapi.json | Updates OpenAPI snapshot with saved-views endpoints/schemas and org features field. |
| src/Exceptionless.Web/Models/SavedView/ViewSavedView.cs | Adds SavedView response DTO. |
| src/Exceptionless.Web/Models/SavedView/UpdateSavedView.cs | Adds update DTO + column-key validation hook. |
| src/Exceptionless.Web/Models/SavedView/NewSavedView.cs | Adds create DTO + view/JSON/column validation. |
| src/Exceptionless.Web/Models/Organization/ViewOrganization.cs | Exposes organization Features set to clients. |
| src/Exceptionless.Web/Mapping/SavedViewMapper.cs | Adds Mapperly mapper for SavedView <-> DTOs. |
| src/Exceptionless.Web/Mapping/ApiMapper.cs | Wires SavedView mappings into API mapper facade. |
| src/Exceptionless.Web/Controllers/SavedViewController.cs | Implements saved-views endpoints with feature gating, scoping, and default handling. |
| src/Exceptionless.Web/Controllers/OrganizationController.cs | Removes private saved views on org user removal; adds feature enable/disable endpoints. |
| src/Exceptionless.Web/ClientApp/src/routes/routes.svelte.ts | Extends navigation types to support dashboard children entries. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte | Adds saved-view query param + picker integration for Stream dashboard. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/routes.svelte.ts | Adds “Features” admin route entry. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/features/+page.svelte | New UI page for toggling org feature flags. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte | Adds saved-view query param + picker integration for Issues dashboard. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte | Adds saved-view query param + picker integration for Events dashboard. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte | Subscribes to SavedView websocket invalidation + builds sidebar children from saved views. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar.svelte | Renders collapsible dashboard submenu for saved views. |
| src/Exceptionless.Web/ClientApp/src/lib/generated/schemas.ts | Adds generated schemas/types for saved views and org features. |
| src/Exceptionless.Web/ClientApp/src/lib/generated/api.ts | Adds generated API types for saved views and org features. |
| src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/use-saved-views.svelte.ts | Adds saved-view state management (hydrate/reset/modified/default restore). |
| src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/models.ts | Adds saved-views feature-layer types over generated client types. |
| src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/index.ts | Re-exports saved-views APIs/models for feature consumers. |
| src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/components/saved-view-picker.svelte | Adds UI for selecting/saving/renaming/deleting/defaulting saved views. |
| src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/api.svelte.ts | Adds saved-views queries/mutations + websocket invalidation behavior. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts | Adds org feature toggle mutations. |
| src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/helpers.svelte.ts | Adds filter serialization/deserialization for saved-view persistence. |
| src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/helpers.svelte.test.ts | Adds unit tests for filter (de)serialization + round-trips. |
| src/Exceptionless.Core/Services/OrganizationService.cs | Removes saved views on org deletion; removes private views when user leaves org. |
| src/Exceptionless.Core/Repositories/SavedViewRepository.cs | Adds repository queries for org/user/view scoping and cleanup operations. |
| src/Exceptionless.Core/Repositories/Interfaces/ISavedViewRepository.cs | Adds saved-view repository contract. |
| src/Exceptionless.Core/Repositories/Configuration/Indexes/SavedViewIndex.cs | Adds ES index/mapping for saved views. |
| src/Exceptionless.Core/Repositories/Configuration/Indexes/OrganizationIndex.cs | Adds ES mapping for organization features field. |
| src/Exceptionless.Core/Repositories/Configuration/ExceptionlessElasticConfiguration.cs | Registers SavedViews index in elastic configuration. |
| src/Exceptionless.Core/Models/SavedView.cs | Adds SavedView core model + allowed views/columns definitions. |
| src/Exceptionless.Core/Models/Organization.cs | Adds Features set + OrganizationFeatures constants. |
| src/Exceptionless.Core/Bootstrapper.cs | Registers ISavedViewRepository in DI. |
| .agents/skills/typescript-conventions/SKILL.md | Captures project-specific frontend conventions. |
| .agents/skills/frontend-architecture/SKILL.md | Captures project Svelte/architecture guidance (query param binding, $derived/$effect). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (organization is null) | ||
| return NotFound(); | ||
|
|
||
| organization.Features.Add(feature.Trim().ToLowerInvariant()); |
There was a problem hiding this comment.
feature.Trim().ToLowerInvariant() can become an empty string for a whitespace-only {feature} path segment (minlength(1) still allows this), which would store/remove an empty feature flag value. Add a post-trim validation (e.g., if empty -> 400) before mutating organization.Features.
| organization.Features.Add(feature.Trim().ToLowerInvariant()); | |
| var normalizedFeature = feature?.Trim().ToLowerInvariant(); | |
| if (string.IsNullOrEmpty(normalizedFeature)) | |
| return BadRequest("Invalid feature flag."); | |
| organization.Features.Add(normalizedFeature); |
| if (organization is null) | ||
| return NotFound(); | ||
|
|
||
| if (organization.Features.Remove(feature.Trim().ToLowerInvariant())) |
There was a problem hiding this comment.
Same whitespace-only edge case as SetFeatureAsync: after Trim().ToLowerInvariant(), feature can be empty and Remove("" ) becomes a no-op while still returning 200. Consider validating the normalized feature id and returning 400 for invalid/empty values to avoid silently accepting bad input.
| if (organization.Features.Remove(feature.Trim().ToLowerInvariant())) | |
| var normalizedFeature = feature?.Trim().ToLowerInvariant(); | |
| if (string.IsNullOrWhiteSpace(normalizedFeature)) | |
| return BadRequest("Invalid feature identifier."); | |
| if (organization.Features.Remove(normalizedFeature)) |
| }, | ||
| "columns": { | ||
| "type": "array" | ||
| }, | ||
| "is_default": { |
There was a problem hiding this comment.
UpdateSavedView.columns is documented as an array in the OpenAPI snapshot, but the server DTO (UpdateSavedView.Columns) is a Dictionary<string, bool>? and responses (ViewSavedView.columns) are an object map. This mismatch will generate incorrect client types and can break non-frontend consumers. Update the OpenAPI generation/snapshot so columns is type: ["null","object"] with additionalProperties: { "type": "boolean" } (and optional), consistent with NewSavedView/ViewSavedView.
Fixes for code quality and correctness issues identified in saved-views PR review: - OrganizationController: Add post-trim validation to prevent empty feature flags from whitespace-only input (closes security gap in minlength route constraint) - DeltaSchemaTransformer: Fix Dictionary schema generation to emit object with additionalProperties instead of array (IDictionary detection must run before IEnumerable check) - helpers.svelte.ts: Replace vague `as []` casts with concrete types (LogLevel[], StackStatus[], PersistentEventKnownTypes[]) for type safety - +layout.svelte: Resolve duplicate currentOrganization declaration by renaming Intercom-specific variable - OrganizationControllerTests: Add whitespace-only feature name test coverage - openapi.json: Regenerate snapshot to reflect Dictionary schema fix
Fixes for code quality and correctness issues identified in saved-views PR review: - OrganizationController: Add post-trim validation to prevent empty feature flags from whitespace-only input (closes security gap in minlength route constraint) - DeltaSchemaTransformer: Fix Dictionary schema generation to emit object with additionalProperties instead of array (IDictionary detection must run before IEnumerable check) - helpers.svelte.ts: Replace vague `as []` casts with concrete types (LogLevel[], StackStatus[], PersistentEventKnownTypes[]) for type safety - +layout.svelte: Resolve duplicate currentOrganization declaration by renaming Intercom-specific variable - OrganizationControllerTests: Add whitespace-only feature name test coverage - openapi.json: Regenerate snapshot to reflect Dictionary schema fix
Sidebar and picker now update immediately when creating or saving views by writing to both queryKeys.view and queryKeys.organization caches optimistically. Delayed websocket invalidation still provides ES-refresh-safe reconciliation.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 43 out of 45 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Key Decisions (moved from AGENTS into owned skills)
Why
Test Plan
Dogfood
Browser artifacts
API flow verification
Follow-up for attachments
Notes