Skip to content

refactor(savedview): restructure api and storage to spec based - #12342

Merged
krsoninikhil merged 56 commits into
mainfrom
ns/saved-views-2
Aug 7, 2026
Merged

refactor(savedview): restructure api and storage to spec based#12342
krsoninikhil merged 56 commits into
mainfrom
ns/saved-views-2

Conversation

@krsoninikhil

@krsoninikhil krsoninikhil commented Jul 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Saved views now persist a versioned, typed spec (schemaVersion + spec{compositeQuery, selectedFields, display}) instead of a bare composite-query blob plus an opaque, frontend-owned extraData string -- mirroring the pattern dashboards already use for their v2/perses schema.
  • /api/v1/explorer/views keeps working exactly as before: a thin conversion layer translates to/from the legacy wire format, including folding extraData's ad hoc JSON into the typed spec and back for backward compatibility.
  • A one-time migration rewrites existing rows into the new shape and drops the now-unused extra_data/category/tags columns.

Scaffolding decisions

  • Using v2 for new handlers instead of renaming old handlers to something else for these reasons - keep the diff minimum for easier reviews, avoiding any git history or last updated at change in old route registration.
  • Keeping the conversion to old saved view type in handler itself rather than savedviewtypes package to keep it un-exported and not let them be available anywhere else to be used. It also enables savedviewtypes to be independent on query-service models.
  • Modified the existing handler and it's interface to include the v2 methods instead of adding another handlerV2 since apiserver already had handler wired in, so don't want to pass on 2 version simultaneously.

Breaking change

  • Any unknown key in the ExtraData will be rejected and dropped silently in the old APIs and give error in new version.
  • If there was any way to add tag or category in saved view earlier, that data will be lost.
  • Old APIs will not support the old QB request payload, only v5 format is supported.

Closes SigNoz/engineering-pod#4651

Alternative discarded #12208

krsoninikhil and others added 14 commits July 21, 2026 15:19
feat(savedview): register v2 saved view routes via handler.New()

Registers List/Create/Get/Update/Delete for saved views at
/api/v2/saved_views(/{viewId}) in signozapiserver using handler.New()
with OpenAPIDef, mirroring the alertmanager migration (#10941). This
unblocks audit-log instrumentation and Terraform resource generation,
which the legacy router.HandleFunc registrations in http_handler.go
can't support.

/api/v1/explorer/views keeps working unchanged. Wires the previously
dead addSavedViewRoutes into provider.AddToRouter, adds the missing
authz middleware (ViewAccess/EditAccess, matching the v1 access split),
adds required/nullable OpenAPI tags to v3.SavedView/CompositeQuery, and
regenerates docs/api/openapi.yml.

Refs SigNoz/engineering-pod#4651
Relocates the saved-view domain type from pkg/query-service/model/v3
(v3.SavedView) to pkg/types/savedviewtypes.SavedView, the conventional
home for domain types, following the same Handler/Module signatures.
The existing bun-persisted row type in that package is renamed from
SavedView to StorableSavedView to avoid a name collision (matching the
Storable*/domain-type naming convention used elsewhere, e.g.
dashboardtypes.StorableDashboard).

v3.SavedView was only referenced by the savedview module/handler
(verified via grep), so this is a mechanical move -- CompositeQuery
itself stays in model/v3 since ~25 other files depend on it.

No new handler methods or request/response types: the v2 routes added
in the previous commit keep using the same Create/Get/Update/Delete/
List methods as /api/v1/explorer/views. CompositeQuery already has
omitempty legacy (builderQueries/chQueries/promQueries) and v5
(queries) fields side by side, and Validate() already accepts a
v5-only payload, so one type/handler pair genuinely serves both API
versions -- no conversion layer needed.

Refs SigNoz/engineering-pod#4651
Splits the domain type into request/response shapes so create/update
don't require id/createdAt/createdBy/updatedAt/updatedBy from the
client:

- PostableSavedView: request body for create/update (no id or
  server-populated audit fields).
- UpdatableSavedView: alias of PostableSavedView (a saved view is
  always replaced in full).
- GettableSavedView: response shape for get/list/create's-echo,
  carrying id and audit fields.

Module and Handler interfaces updated accordingly (CreateView/
UpdateView take PostableSavedView/UpdatableSavedView, GetView/
GetViewsForFilters return *GettableSavedView). The Update handler now
re-fetches and returns the persisted view instead of echoing the
request body, since callers (including the existing frontend type,
UpdateViewPayloadProps.data: ViewProps) expect id/timestamps back.

Also, per the v5-only typing work this continues:
- CompositeQuery is the new name for the saved-view query type
  (matches the established qbtypes.CompositeQuery naming), replacing
  the legacy v3.CompositeQuery for this domain.
- pkg/query-service/model/v3/v3.go now only differs from origin/main
  by the SavedView struct removal (and its now-unused valuer import)
  -- no stray struct tags left over from earlier iterations.
- Validate() uses errors.NewInvalidInputf with a package error code
  instead of fmt.Errorf, matching the forbidigo-clean pattern used
  elsewhere (e.g. dashboardtypes).
- pkg/types/savedviewtypes consolidated down to query.go and
  savedview.go; the separate list.go/domain.go files are gone.

Verified with go build, golangci-lint (0 issues), an openapi.yml
regeneration (clean diff), and a live create/get/update/list/delete
smoke test against /api/v2/saved_views.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
savedviewtypes.CompositeQuery used pkg/query-service/model/v3.PanelType
and v3.QueryType, pulling in the legacy package purely for two small
enums. Alerting already solved this exact problem for its own
qbtypes.QueryEnvelope-based composite query (ruletypes.PanelType/
QueryType in pkg/types/ruletypes/alerting.go) -- mirror that pattern
here instead:

- savedviewtypes.PanelType: local enum with 5 values (value/graph/
  table/list/trace). ruletypes.PanelType only has 3 (value/table/
  graph), which isn't enough for saved views -- log/trace explorer
  views need list/trace too.
- savedviewtypes.QueryType: local enum (builder/clickhouse_sql/
  promql). This is a UI-tab-selector concept (which query-builder mode
  the view was last edited in), distinct from qbtypes.QueryEnvelope.Type
  (the per-query envelope discriminator, e.g. builder_query/
  builder_formula/clickhouse_sql/promql, which can differ across
  entries in the same Queries array). Keeping it, just re-typed
  locally instead of importing v3 for it.

Bonus: unlike v3.PanelType (a bare Go string with no schema enum),
these implement jsonschema.Enum, so the generated OpenAPI spec now
lists the acceptable values instead of a bare `type: string`.

Verified with go build, golangci-lint (0 issues), an openapi.yml
regeneration (diff is just the two new enum schemas plus $ref swaps),
and a live smoke test confirming the wire format is unchanged and
invalid panelType/queryType values are rejected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
sourcePage was a bare string. The frontend only ever sends one of 4
values -- traces/logs/metrics (its own DataSource enum) plus a
special-cased "meter" string literal for the meter explorer -- matching
the legacy v3.DataSource enum exactly. Category has no such fixed set
(unused by the frontend entirely, always empty) so it stays a string.

SourcePage is local to savedviewtypes (valuer.String-backed, same
pattern as PanelType/QueryType), validated on PostableSavedView.Validate(),
and used directly as the StorableSavedView bun column type -- valuer.String
already implements driver.Valuer/sql.Scanner so no extra plumbing is
needed for it to round-trip through the DB.

Verified with go build, golangci-lint (0 issues), an openapi.yml
regeneration (sourcePage now has a proper enum schema instead of a bare
string, in both the request/response bodies and the List query param),
and a live smoke test: valid sourcePage round-trips through create/get/
list, invalid values are rejected on create.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
…types

implsavedview/module.go was hand-building StorableSavedView/GettableSavedView
struct literals and doing json.Marshal/Unmarshal + strings.Join/Split inline
in every method. Mirrors the tagtypes convention (NewTag,
NewGettableTagFromTag/NewGettableTagsFromTags) instead:

- NewStorableSavedView(orgID, createdBy, PostableSavedView) (*StorableSavedView, error)
  builds the DB row from a request, generating id/timestamps.
- NewGettableSavedViewFromStorable(*StorableSavedView) (*GettableSavedView, error)
  and NewGettableSavedViewsFromStorable (batch) build the API response,
  unmarshalling the JSON-encoded query blob.

UpdateView reuses NewStorableSavedView too -- it only pulls Name/Category/
SourcePage/Tags/Data/ExtraData/UpdatedAt/UpdatedBy out of the result for its
column-level Set(), so the throwaway id/createdAt/createdBy it also computes
are harmless.

No behavior change: verified with go build, golangci-lint (0 issues), a
no-op openapi.yml regeneration (pure internal refactor, no type-shape
change), and a live create/get/update/list/delete smoke test confirming
createdAt/createdBy survive an update while updatedAt changes, and tags/
category/compositeQuery all round-trip correctly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
…View

NewStorableSavedView took a single createdBy param and used it for both
CreatedBy and UpdatedBy, which is only correct for a fresh create. Split
it into separate createdBy/updatedBy args so a caller can vary them
independently (e.g. an update path that only needs to bump updatedBy).
Both CreateView and UpdateView currently pass claims.Email for both --
UpdateView's result is used to patch update_at/update_by plus the
content columns via Set(), so the throwaway id/createdAt/createdBy it
computes stay unused there, same as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
Both are unused: category is set nowhere in the frontend and read
nowhere either (never in SaveViewProps/UpdateViewProps, only appears
in the response-only ViewProps type); tags has no write path at all
(no UI to add them) -- the one place it's read (a homepage widget
badge list) already guards against the empty-string artifact this
produces, confirming nothing ever populates it.

Keeping StorableSavedView.Category/.Tags as-is (still NOT NULL text
columns) to avoid a migration in this PR -- marked `// TODO:
deprecated, remove it` for a follow-up that drops the columns.
NewStorableSavedView/NewGettableSavedViewFromStorable no longer
read/write them, so new rows get empty-string defaults same as before
tags was ever set, and existing rows' values are simply never
surfaced.

Verified with go build, golangci-lint (0 issues), an openapi.yml
regeneration (clean field removal from both request/response schemas),
and a live create/get/update/list/delete smoke test with no category/
tags in the payload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
…a struct

category was never passed by the frontend on any List call -- confirmed
via grep, no call site sets it. Drop it from ListSavedViewsParams and
the category-branching query in GetViewsForFilters.

Also switch List's handler to the binding.Query.BindQuery(...) +
params.Validate() pattern already used by dashboard v2's list handler
(pkg/modules/dashboard/impldashboard/v2_handler.go), instead of manually
pulling each field off r.URL.Query(). GetViewsForFilters now takes the
whole *ListSavedViewsParams instead of separate sourcePage/name/category
strings, matching Module.ListV2's params-struct signature.

ListSavedViewsParams.Validate() skips the SourcePage check when it's
zero (unset), consistent with how ListSort/ListOrder handle optional
enum query params in ListFilter.Validate() -- but validates it strictly
otherwise, so an invalid sourcePage on List now returns a clear error
instead of silently matching zero rows.

Verified with go build, golangci-lint (0 issues), an openapi.yml
regeneration (category query param removed, clean diff), and a live
List smoke test: valid sourcePage filters correctly, no sourcePage
returns empty (unchanged from before), invalid sourcePage is now
rejected with a validation error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
Passing the whole *ListSavedViewsParams through to the module ties the
module's signature to the handler's request-binding shape. Revert to
individual args (sourcePage, name) -- the handler still binds/validates
via ListSavedViewsParams, it just unpacks before calling into the
module.

Verified with go build, golangci-lint (0 issues), no openapi.yml diff
(pure module-boundary change), and a live List smoke test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
Regenerated via: go run cmd/enterprise/*.go generate openapi && cd frontend && pnpm generate:api
docs/api/openapi.yml had no diff (already up to date from prior commits
this session); the frontend generated schemas pick up the savedview
type changes (SourcePage/PanelType/QueryType enums, dropped category/
tags).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
No consumer needs the full view back from Update: neither active
frontend caller of useUpdateView (ExplorerOptions.tsx, pages/SaveView/
index.tsx) reads the mutation's response data -- both just check
success and separately call refetchAllView(). The signoz-mcp-server
does its own GetView before calling Update for its own validation, so
it doesn't depend on Update's response either.

The GetView call was, however, the only thing making PUT on a
nonexistent view id return an error -- bun's raw UPDATE with a
non-matching WHERE clause doesn't error on its own, so removing the
extra fetch without replacement would have silently turned "update a
bogus id" into a fake success. Replaced it with a RowsAffected() check
on the UPDATE result instead (same pattern already used in
llmpricingrule/spanmapper stores) -- no extra DB round trip, and it's
strictly more correct: a bogus id now returns a proper "not found"
error (matching the ErrorStatusCodes: 404 already declared on
UpdateSavedView's OpenAPIDef, which wasn't actually enforced before).

Response for Update is now nil (matching Delete's convention), since
nothing consumes the body.

Verified with go build, golangci-lint (0 issues), and a live smoke
test: a normal update still persists (confirmed via a follow-up GET),
and updating a nonexistent id now returns saved_view_not_found instead
of a silent success. Also re-verified /api/v1/explorer/views (same
shared handler) is unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
Regenerated via: go run cmd/enterprise/*.go generate openapi && cd frontend && pnpm generate:api
Picks up UpdateSavedView's response becoming void (no body).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
@krsoninikhil krsoninikhil changed the title feat(savedview): restructure storage around typed schemaVersion+spec feat(savedview): restructure storage to be spec based Jul 30, 2026
@krsoninikhil krsoninikhil changed the title feat(savedview): restructure storage to be spec based feat(savedview): restructure api and storage to spec based Jul 30, 2026
v3.SavedView was removed from this package earlier in this branch when
the saved-view domain type moved to savedviewtypes. Restoring it here
so /api/v1/explorer/views can decode/encode the exact legacy wire shape
via a thin converter, instead of duplicating an equivalent struct.
@krsoninikhil
krsoninikhil force-pushed the ns/saved-views-2 branch 6 times, most recently from 2b1d001 to 4f766d6 Compare July 30, 2026 12:32
krsoninikhil and others added 4 commits July 30, 2026 18:23
Mirrors the dashboardtypes v2 spec pattern: StorableSavedView.Data is
now a typed SavedViewData{SchemaVersion, Spec} (bun auto-marshals it
like DashboardView.Data), and the canonical GettableSavedView/
PostableSavedView embed it directly, replacing the old flat
CompositeQuery+ExtraData shape. SavedViewSpec adds SelectedFields and
Display{MaxLines,FontSize,Format,Color}, formalizing what the frontend
previously packed into an opaque, backend-unaware extraData JSON
string.

Handler splits into canonical (Create/Get/Update/List, backing
/api/v2/saved_views) and legacy (CreateV1/GetV1/UpdateV1/ListV1,
backing /api/v1/explorer/views) methods; Delete is shared since it has
no request/response body to reshape. The legacy methods decode/encode
v3.SavedView directly and convert to/from the canonical spec via small
converters in implsavedview -- extraData is best-effort parsed into
selectedFields/display on write and re-synthesized on read, so the
still-live frontend keeps working unchanged. CreateV1/UpdateV1 validate
via v3.SavedView.Validate() (no sourcePage enum enforcement), matching
production main's behavior exactly rather than the stricter canonical
validation.

Module stays singular (CreateView/GetView/UpdateView/
GetViewsForFilters/DeleteView) -- only the wire-facing handler differs
per API generation, so the two generations always converge on the same
storage shape, unlike dashboard's v1/v2 split where the DB can hold two
different JSON shapes depending on which API wrote a row.
One-time migration (mirrors 046's tx-based read/transform/write-back
structure): rewrites every existing saved_views.data row from the bare
CompositeQuery blob into the new {schemaVersion, spec} envelope,
best-effort folding legacy extra_data content into
spec.selectedFields/spec.display. Then drops the now-unused extra_data,
category, and tags columns (confirmed via earlier research that
category/tags were never actually populated by any caller).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Display's fields (maxLines/fontSize/format/color) and
SavedViewSpec.SelectedFields used omitempty, which silently drops a
zero-valued field from the response (e.g. an explicit maxLines:0 would
vanish from a subsequent GET). Matches dashboardtypes v2's own
convention: required fields never carry an omit option, so a response
value can never be confused with an omitted key.

SelectedFields/Queries also gain nullable:false since they're now
always present -- NewGettableSavedViewFromStorable normalizes a nil
SelectedFields to an empty slice so the response never emits `null`
for it (Queries needs no such guard: CompositeQuery.Validate() already
rejects an empty query list before a row can be persisted).

Verified live: creating a view with explicit maxLines:0/empty
selectedFields, and a legacy v1-created view with no extraData at all,
both round-trip with selectedFields:[] and the zero-valued display
fields intact, never omitted.
krsoninikhil and others added 3 commits August 6, 2026 15:20
saved-view has no attach/detach semantics -- nothing links to it the
way roles attach to service accounts. Restrict
ResourceMetaResourceSavedView to the 5 CRUD verbs it actually
supports, matching ResourceMetaResourceFactorAPIKey's pattern, so a
custom role can no longer be granted attach/detach on saved-view and
those verbs don't surface as options wherever saved-view's
permissions get exposed.

Verified: a custom role transactionGroup granting "attach" on
metaresource:saved-view now gets rejected with 400 ("verb attach is
not valid for resource metaresource:saved-view") instead of being
silently accepted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb5R7Eo19HUukBMVCgKPjS
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread pkg/sqlmigration/109_restructure_saved_view_spec.go
Comment thread pkg/apiserver/signozapiserver/savedview.go Outdated
Comment thread pkg/apiserver/signozapiserver/savedview.go Outdated
Comment thread pkg/apiserver/signozapiserver/savedview.go Outdated
Comment thread pkg/types/savedviewtypes/savedview.go
krsoninikhil and others added 6 commits August 7, 2026 16:09
Runtime handlers now actually return 201/204 to match the documented
OpenAPI spec (the doc-only change previously left CreateV2/UpdateV2/
Delete still returning 200 with a body).
Per review feedback: an empty name silently generating a slug was a
round-trip hazard. generateName must now be explicit, mirroring
PostableDashboardV2.
Reflects both the corrected 201/204 status codes and the new
generateName field.
Follows the 057_rename_org_domains pattern: read old rows, drop
saved_views, create saved_view with the final schema, insert
transformed rows, then add the unique index.
UpdateView no longer fetches the existing row before writing --
store.Update already scopes by id+orgID and already reports
not-found via RowsAffected() == 0, so the fetch was pure overhead.
UpdatableSavedView.ToSavedView builds the row to write directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnYR48DXJQGxqQSEW3237h
@krsoninikhil
krsoninikhil added this pull request to the merge queue Aug 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

PR removed from merge queue

@krsoninikhil your PR was removed from the merge queue. Fix the issue and re-queue when ready.

@krsoninikhil
krsoninikhil added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit e08ef01 Aug 7, 2026
20 checks passed
@krsoninikhil
krsoninikhil deleted the ns/saved-views-2 branch August 7, 2026 13:48
pull Bot pushed a commit to erickirt/signoz that referenced this pull request Aug 8, 2026
… 109 (SigNoz#12469)

## Summary
- `restructureSavedViewSpec` (migration 109) bulk-inserts legacy
`saved_views` rows into the new `saved_view` table, which has an
enforced `org_id -> organizations(id)` FK (sqlite runs with
`foreign_keys=ON`).
- Some tenants hit `constraint failed: FOREIGN KEY constraint failed
(787)` on startup because a row's `org_id` didn't match any live
organization -- e.g. an org deleted before the old table had a cascading
FK, or an install where `org_id` was never backfilled
(`015_update_dashboards_savedviews` only backfills it when there's
exactly one org).
- Fix: fetch live organization IDs up front inside the same transaction,
and skip (with a `WarnContext` log, counted in `skipped`) any row whose
non-empty `org_id` isn't among them -- same treatment as the existing
empty-`org_id` skip.

Followup to SigNoz#12342

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
krsoninikhil added a commit that referenced this pull request Aug 9, 2026
tests/integration/tests/savedview/ has existed since #12342 but was never
added to the CI matrix, so it has never actually run in CI -- which is how
its request/response shapes were able to drift out of sync with the API
without anything failing (see the preceding fix(tests) commit).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnYR48DXJQGxqQSEW3237h
pull Bot pushed a commit to psy-repos-typescript/signoz that referenced this pull request Aug 11, 2026
## Summary
- Handle malformed selectedFields in the extradata in the migration and
new migration to fix in the already migrated cases.
- Restructure saved-view create/update/get payloads so
`schemaVersion`/`spec` are top-level (unwrapping the old `data`
nesting), matching how dashboards and rules shape their wire types.
- Publish `schemaVersion` as an `enum: [v2]`
- Make `display` and `selectedFields` optional in the OpenAPI schema
- Declare `409` on `CreateSavedView`
- Require `minItems: 1` on `queries`

New API contract in [below
comment](SigNoz#12477 (comment)),
follow up on SigNoz#12342
Closes SigNoz/engineering-pod#4651

Notes to reviewer: 
- Please pay attention to the last case in above linked comment for
partial display field updates.
- Still assuming that [migration
046](https://github.com/SigNoz/signoz/blob/6372af75a6e08375a2c92feac94e237462d83304/pkg/sqlmigration/046_update_dashboard_alert_and_saved_view_v5.go#L233)
has already migrated all the views to v5 QB format and don't need to do
that now.
- Breaking change: queries are not validated in the v1 APIs as well, so
any incorrect query will be rejected

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
tewarig pushed a commit that referenced this pull request Aug 12, 2026
## Summary
- Handle malformed selectedFields in the extradata in the migration and
new migration to fix in the already migrated cases.
- Restructure saved-view create/update/get payloads so
`schemaVersion`/`spec` are top-level (unwrapping the old `data`
nesting), matching how dashboards and rules shape their wire types.
- Publish `schemaVersion` as an `enum: [v2]`
- Make `display` and `selectedFields` optional in the OpenAPI schema
- Declare `409` on `CreateSavedView`
- Require `minItems: 1` on `queries`

New API contract in [below
comment](#12477 (comment)),
follow up on #12342
Closes SigNoz/engineering-pod#4651

Notes to reviewer: 
- Please pay attention to the last case in above linked comment for
partial display field updates.
- Still assuming that [migration
046](https://github.com/SigNoz/signoz/blob/6372af75a6e08375a2c92feac94e237462d83304/pkg/sqlmigration/046_update_dashboard_alert_and_saved_view_v5.go#L233)
has already migrated all the views to v5 QB format and don't need to do
that now.
- Breaking change: queries are not validated in the v1 APIs as well, so
any incorrect query will be rejected

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
tewarig pushed a commit that referenced this pull request Aug 12, 2026
## Summary
- Handle malformed selectedFields in the extradata in the migration and
new migration to fix in the already migrated cases.
- Restructure saved-view create/update/get payloads so
`schemaVersion`/`spec` are top-level (unwrapping the old `data`
nesting), matching how dashboards and rules shape their wire types.
- Publish `schemaVersion` as an `enum: [v2]`
- Make `display` and `selectedFields` optional in the OpenAPI schema
- Declare `409` on `CreateSavedView`
- Require `minItems: 1` on `queries`

New API contract in [below
comment](#12477 (comment)),
follow up on #12342
Closes SigNoz/engineering-pod#4651

Notes to reviewer: 
- Please pay attention to the last case in above linked comment for
partial display field updates.
- Still assuming that [migration
046](https://github.com/SigNoz/signoz/blob/6372af75a6e08375a2c92feac94e237462d83304/pkg/sqlmigration/046_update_dashboard_alert_and_saved_view_v5.go#L233)
has already migrated all the views to v5 QB format and don't need to do
that now.
- Breaking change: queries are not validated in the v1 APIs as well, so
any incorrect query will be rejected

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
tewarig pushed a commit that referenced this pull request Aug 12, 2026
## Summary
- Handle malformed selectedFields in the extradata in the migration and
new migration to fix in the already migrated cases.
- Restructure saved-view create/update/get payloads so
`schemaVersion`/`spec` are top-level (unwrapping the old `data`
nesting), matching how dashboards and rules shape their wire types.
- Publish `schemaVersion` as an `enum: [v2]`
- Make `display` and `selectedFields` optional in the OpenAPI schema
- Declare `409` on `CreateSavedView`
- Require `minItems: 1` on `queries`

New API contract in [below
comment](#12477 (comment)),
follow up on #12342
Closes SigNoz/engineering-pod#4651

Notes to reviewer: 
- Please pay attention to the last case in above linked comment for
partial display field updates.
- Still assuming that [migration
046](https://github.com/SigNoz/signoz/blob/6372af75a6e08375a2c92feac94e237462d83304/pkg/sqlmigration/046_update_dashboard_alert_and_saved_view_v5.go#L233)
has already migrated all the views to v5 QB format and don't need to do
that now.
- Breaking change: queries are not validated in the v1 APIs as well, so
any incorrect query will be rejected

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request safe-to-integrate Run integration tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants