Guard ErrorResponseHandlerForGeneratedAPIs like ErrorResponseHandlerV2: when
response.data has no error object (gateway 5xx with HTML/empty body during a
deploy), synthesize an UPSTREAM_UNAVAILABLE APIError instead of throwing on
response.data.error.code. A present error object (even without a code) is
still read, so real backend messages are preserved.
toAPIError now treats UPSTREAM_UNAVAILABLE (no backend code found) as 'could
not parse' and falls back to the caller's context-specific defaultMessage,
preserving the ServiceAccount/Roles error UX that previously relied on the
handler crashing.
Closes https://github.com/SigNoz/engineering-pod/issues/5761
The deprecated ErrorResponseHandlerForGeneratedAPIs guard broke toAPIError's
defaultMessage fallback (which relied on the handler crashing), regressing the
error UX in ServiceAccount/Roles screens. Moved to a stacked PR + tracked in
engineering-pod#5761. Keeps this PR scoped to ErrorResponseHandlerV2 +
convertToApiError, and the strengthened V2 handler tests remain.
- Guard the deprecated ErrorResponseHandlerForGeneratedAPIs against a
non-envelope response body (gateway 5xx with HTML/empty body), mirroring
ErrorResponseHandlerV2 — falls back to UPSTREAM_UNAVAILABLE instead of
throwing on response.data.error.code.
- Parametrize the V2 handler tests into an { error, expected } table and
assert the sub-error 'errors' messages, which several UI surfaces rely on.
convertToApiError now returns UPSTREAM_UNAVAILABLE (not a stringified
status) when the response carries no error code; update the panelStatus
fallback assertion to match.
When the response body carries no error code, fall back to a stable
UPSTREAM_UNAVAILABLE code instead of a stringified HTTP status, mirroring
the ErrorResponseHandlerV2 fallback.
Address review: when the response body isn't a V2 envelope (e.g. a gateway
5xx during a deploy), throw a stable UPSTREAM_UNAVAILABLE code instead of
the stringified HTTP status, and trim the guard comment.
Restore the AxiosError<ErrorV2Resp> signature so it documents that this is
the V2 error handler, and confine the runtime uncertainty to the one spot
that matters: launder response.data through a local unknown binding before
narrowing it with the isErrorV2Resp guard.
The handler assumed every non-2xx response carried the V2 error envelope
and read response.data.error.code directly. During a deployment the
gateway returns a 5xx with an HTML/empty body, so response.data.error is
undefined and the handler threw its own TypeError, masking the real
failure and crashing the caller.
Type the inbound body as unknown and narrow it with an isErrorV2Resp type
guard; when the body isn't a V2 envelope, synthesize an APIError from the
HTTP status instead of throwing. Callers already pass
AxiosError<ErrorV2Resp>, which stays assignable to AxiosError<unknown>.
* fix(session): use global external_url instead of ref param for SSO state
The sessions/context endpoint no longer reads the client-controlled ref
query param to build the SSO state and callback URLs. Each callback
authn provider now derives the site URL from the server-configured
global external_url, and siteURL is removed from the CallbackAuthN and
session Module interfaces.
* fix(session): validate ref and callback state against global allowed_origins
Instead of deriving SSO urls from the global external_url, keep the ref
roundtrip and validate its origin against the new optional
global.allowed_origins config. The callback state url is re-validated
before tokens are attached to it, closing the forged RelayState/state
exfiltration path. When allowed_origins is not configured, redirect
targets are not validated, preserving existing installs.
* fix(session): scope ref origin validation to sso auth domains
Move the allowed_origins check from the sessions/context handler to
getOrgSessionContext, right before the SSO login URL is built. A
disallowed ref no longer fails the whole request; only orgs with an
SSO-enabled auth domain get a per-org warning with password fallback.
* feat(share): support a page-specific extra option in the share dialog
* feat(dashboard-v2): share a dashboard link with the current variable selection
* fix(dashboards-v2): carry active time window across panel editor navigation
Opening, creating, or leaving the V2 panel editor now preserves the selected time
range (relative or custom) via URL params derived from Redux global time, so a
custom range picked in the editor isn't reset to the dashboard default on return.
Adds timeParamsFromGlobalTime + useTimeSearchParams; useOpenPanelEditor now takes an
options object ({ handoffState, search }) and appends the time params, and
useCreatePanel routes through it.
* fix(dashboards-v2): sync panel editor preview to the staged query on browser back/forward
The query builder reverts both currentQuery and stagedQuery via initQueryBuilderData on a
URL re-stage (chiefly browser Back/Forward), but the preview kept the last Run's result.
Commit the staged query into the draft whenever it re-stages outside an explicit Run, so the
preview follows. Live edits touch only currentQuery, so they still wait for Run; commitQuery
no-ops when unchanged.
* fix(dashboards-v2): stop query-builder dropdowns being clipped in the panel editor
The panel editor renders the query builder inside an overflow:hidden resizable pane, so
antd Select popups (group-by, order-by, having, metric name, aggregator, units) were cut
off. Make the shared QB filters defer to an ancestor ConfigProvider's popup container
(falling back to trigger.parentNode) via useSelectPopupContainer, and wrap the editor's
builder in a ConfigProvider that portals popups to document.body. Scoped to the editor
host, so the View modal keeps its own container and other surfaces are unchanged.
* fix(dashboards-v2): anchor panel editor dirty check to saved panel; retain query edits on refresh
The editor re-derived its dirty baseline from the incoming panel prop, which
is seeded from transient state — a stale URL compositeQuery after a refresh or
the View-mode handoff spec — instead of the persisted panel. So the draft was
compared against an already-edited baseline: after a refresh the panel showed
the saved query yet read dirty (inverted), and a View->Edit query change read
clean with Save disabled.
- Thread a savedPanel baseline (existingPanel) through PanelEditorPage ->
PanelEditorContainer -> usePanelEditSession, distinct from the seed panel.
- usePanelEditorDraft compares isSpecDirty against savedPanel; the draft still
seeds from the (possibly handed-off) panel.
- usePanelEditorQuerySync computes isQueryDirty as a V5-envelope comparison of
the live query against the saved queries, routing both sides through the same
fromPerses->toPerses round-trip so builder-added defaults absent from an older
stored query never read an untouched panel as modified. Replaces the racy
captured-first-stagedQuery baseline.
- Drop the mount forceReset on useShareBuilderUrl (both its reasons are now
moot) so a refresh / browser Back-Forward hydrates the edited query from the
URL and the staged-query effect syncs it into the draft — query edits survive
a refresh instead of reverting to the saved query.
Add real-QueryBuilderProvider integration tests covering untouched-not-dirty
(incl. an older/minimal stored query), refresh retention, and handoff-dirty;
update the draft + query-sync unit tests.
* feat(authz): provision telemetry roles with plaintext selectors and hashed tuples
Accept a user-facing telemetry selector string (<query_type>/<key>/<value>
with trailing wildcards), validate and canonicalize it, store it as-is in the
role JSON record, and hash it only at the OpenFGA boundary in Object(). Grant
and check both flow through Object() so the hashes match; the plaintext record
stays the readable source of truth for display and recreation.
Because the hash is one-way, role Update diffs at the tuple level via
DiffTuples instead of reconstructing transaction groups from stored tuples.
* refactor(authz): rename telemetry selector helpers and unexport hash
Rename grant_selector.go to selector.go, CanonicalizeTelemetryGrantSelector to
NewTelemetryGrantSelector, and CanonicalTelemetryGrantKey to NewTelemetryGrantKey.
Unexport telemetrySelectorHash since Object() is its only caller.
* fix(authz): expand telemetry ladder in the permission check API
The check API only probed the exact selector plus the full wildcard, so a
scoped grant like builder_query/service.name/* reported a concrete query as
unauthorized even though enforcement allowed it. Relocate the grant selector
ladder to telemetrytypes as NewTelemetryGrantSelectors so both enforcement and
the check API share it, and canonicalize plus fan out the ladder for telemetry
check transactions. Non-telemetry resources keep the exact-plus-wildcard probe.
* refactor(authz): move newCheckSelectors to the bottom of tuple.go
* fix(authz): reject key-scoped selectors for promql and clickhouse_sql
Only builder_query and builder_sub_query extract key-scoped selectors on the
check side, so a grant like clickhouse_sql/service.name/signoz could never
match anything. Track key-scope support per query type and reject the
<query_type>/<key>/<value> form for query types that only emit <query_type>/*.
* test(authz): use a valid promql selector in the check API test
The promql/service.name/service-a case became invalid input after key-scoped
promql/clickhouse_sql selectors were rejected, so the check endpoint returned
400 instead of 200. Use promql/* to keep exercising the different-query-type
denial with a valid selector.
* feat(authz): allow signoz.workspace.key.id as a telemetry grant key
Add signoz.workspace.key.id to the telemetry grant key allowlist so roles can
scope telemetry access by ingestion key, alongside service.name. Grant
validation and check-side extraction both pick it up through the shared
NewTelemetryGrantKey path; bare and resource.-prefixed spellings fold to the
same canonical key.
* Revert "feat(authz): allow signoz.workspace.key.id as a telemetry grant key"
This reverts commit c50d122b54.
* feat(authz): scope telemetry grants by signoz.workspace.key.id
Make signoz.workspace.key.id the sole telemetry grant key instead of
service.name, so roles scope telemetry access by ingestion key. The allowlist
is the only production change; grant validation and check-side extraction are
name-agnostic. Update the querierauthz integration suite and unit tests to the
new key.
* test(authz): update query_range_resources extractor tests to signoz.workspace.key.id
The grant-key switch left this extractor test filtering on and expecting
builder_query/service.name/... IDs, which now fall back to builder_query/*.
Rename the filters and expectations to signoz.workspace.key.id.
Temporal Cloud Metrics scrapes an OpenMetrics endpoint but only showed
under APM/Traces, bundled with the Go and TypeScript SDKs. Split it into
a standalone metrics card and trimmed the Temporal APM card to Go and
TypeScript.
ClosesSigNoz/growth-pod#1122
* fix(metrics): resolve any filter key context instead of erroring
The metrics condition builder hard-errored ("key <name> not found") whenever a
filter key was absent from the metadata keys. This hit the full-text search
column (metric_name in the Metrics Explorer, labels in query_range) and every
explicit label context (resource./scope./attribute.), so a bare/partial filter
token 400'd the Metrics Explorer treemap and metric queries.
Metrics keeps every label in the `labels` JSON with no per-context storage, so
FieldFor already maps any key on its own (intrinsics incl. metric_name to their
column, every label context to a labels JSON extract). Use the key directly in
the len(keys)==0 branch so filters resolve and run instead of 400ing - matching
group-by, which already collapses all label contexts to labels.
Update the label-context test to expect the collapse (resource./scope. now
resolve like bare/attribute.), add unit coverage for the branch, and add a
queriermetrics test that a free-text filter no longer errors.
* fix(metrics): resolve free-text and unregistered filter keys instead of erroring
The metrics condition builder hard-errored ("key <name> not found") whenever a
filter key was absent from the metadata keys: the full-text search column
(metric_name on the Metrics Explorer, labels on query_range) and every explicit
label context (resource./scope.). So a bare/partial filter token 400'd the
Metrics Explorer treemap and metric query-builder filters.
Metrics has no per-context storage - every label lives in the `labels` JSON, and
FieldFor maps any key on its own (intrinsics incl. metric_name to their column,
every label context to a labels extract). Add an opt-in WithIgnoreNotFoundKeys
to the metrics condition builder that resolves such keys directly (matching
nothing) instead of erroring:
- query-builder (querier provider) and Metrics Explorer opt in, so free-text and
unregistered contexts resolve - matching the statement builder's own fallback
and group-by, which already collapses all label contexts to labels;
- infra-monitoring list APIs keep the default (strict), so a typo'd attribute
still surfaces as a 400.
Intrinsics always resolve regardless of the flag. Adds unit coverage for both
modes and a queriermetrics test that a free-text filter no longer errors; the
label-context test now expects the collapse.
* chore: updated integration tests
* fix: dont assert warning == []
* chore: removed warning scaffolding for now
* fix: add back removed integration test
---------
Co-authored-by: nikhilmantri0902 <nikhil.mantri1999@gmail.com>
The read client reduced every __name__ matcher to metric_name = <value>
(empty string when absent), so nameless selectors ({job="api"}), regex
names ({__name__=~".+"}), and negations silently returned empty. Map the
__name__ matcher onto the metric_name column per matcher type, drop the
condition when no __name__ matcher exists, and narrow the samples query
by the metric names the series lookup discovered.
Matcher regexes are now anchored (^(?:...)$) the way Prometheus compiles
them; ClickHouse match() is a substring search, so {job=~"api"} also
matched job="api-server" before.
The lookup and samples SQL is built with huandu/go-sqlbuilder (ClickHouse
flavor) as in telemetrymetrics and the v2 transpiler: the samples query
embeds the series lookup as a nested builder, with argument order merged
by the builder in render position instead of hand-numbered placeholders.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
A span/log query that aggregates or filters a numeric intrinsic column
(e.g. duration_nano) failed with ClickHouse NO_COMMON_TYPE (386) when a
same-named, type-consistent attribute existed in the metadata: field-key
resolution unioned the intrinsic column with the attribute into a multiIf/OR
whose branches had incompatible types (UInt64 intrinsic vs Float64 attribute).
This broke /api/v1/span_percentile on affected tenants.
- fallback_expr: DataTypeCollisionHandledFieldName now handles the unspecified
data type in the projection/aggregation path (operator Unknown), coercing the
column so collision multiIf branches share a supertype. Comparison contexts
are left bare so the column index stays usable.
- telemetrytraces/condition_builder: run collision handling for duration_nano
(so a same-named string attribute is cast in comparisons) and coerce numeric
duration values to int64, keeping the intrinsic comparison bare/index-friendly
while preserving the duration-string QoL parsing.
- tests: add a type-consistent "collision" trace_noise variant; cover it in the
percentile aggregation test and add a duration_nano QoL filter regression test.
* fix(querybuildertypesv5): omit unset stepInterval on the wire
Step is a struct (struct{ time.Duration }), so omitempty had no effect — an
unset stepInterval serialized as 0 instead of being omitted, so a typed
client reading a query back saw a 0 it never sent (create -> GET drift).
Tag stepInterval with ,omitzero so an unset value is dropped while a set
value still serializes (as seconds), on all three sites: builder query,
trace-operator, and secondary aggregation. Schema-invisible (no OpenAPI /
client change). source and the metric enums were already handled in #12164.
* fix(telemetrytypes): round-trip empty fieldContext/fieldDataType on field keys
A TelemetryFieldKey can deliberately leave fieldContext/fieldDataType empty
to match across any context / data type, but ,omitzero dropped that empty
value on serialize, so a typed client that sent "" read it back as absent
(create -> GET drift).
Make both fields always serialize and add the empty member to their Enum()s
so "" is a valid schema value that round-trips verbatim — the same approach
#12164 used for source. Signal keeps ,omitzero: its empty value is invalid
for the query/variable signal contexts that share the enum (adding "" there
breaks those consumers), and a field key's signal is not deliberately empty.
Regenerate the OpenAPI spec + client (fieldContext/fieldDataType enums gain
"") and update the ScalarData marshal test (column keys now echo the fields).
* fix(telemetrytypes): round-trip empty signal on field keys
Extend the field-key round-trip fix to Signal: add the empty member to
Signal.Enum() and make TelemetryFieldKey.Signal always serialize, so an
empty ("any") field-key signal round-trips as a valid value instead of
being dropped — matching the fieldContext/fieldDataType treatment.
The Signal enum is shared with query/variable signals, where "" is invalid.
Narrow the frontend's TelemetrySignal type to logs/traces/metrics (so the
variable/panel signal selectors stay exhaustive), label the empty member in
the panel type switcher's map, and fold an empty drilldown signal into "all".
Regenerate the OpenAPI spec + client (Signal enum gains ""), and update the
ScalarData marshal test and the querierlogs aggregation label assertions to
include the now-serialized empty signal.
* feat: add llm o11y to side nav
* chore: rename route
* chore: review comments
* chore: update constants
* chore: change name to ai
* chore: rename draft
* chore: move model pricing out of settings
* chore: update comment
* revert: undo LLM->AI file/dir moves, keep constant rename
Reverts the two file-relocation commits (c25060950 rename LLMObservability
dir -> AIObservability; 1a18bc848 move ModelPricing out of Settings) while
preserving the LLM->AI string/constant renames (route constants + paths,
side-nav entry/labels, page titles, permission keys). Component files stay
under container/LLMObservability with their original names.
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
* fix(dashboardtypes): round-trip zero-valued variable/display fields
omitempty dropped explicit zero values from the create -> GET response, so
a typed client (Terraform/SDK) that sent them read back null and reported
drift. Remove the tag so these always serialize:
- Display.Description ("" round-trips; applies to dashboard/panel/variable
displays)
- TextVariableSpec.Constant (constant: false, like the disabled fix)
- ListVariableSpec.CustomAllValue / CapturingRegexp ("" round-trips)
Scalars carry no nullability, so the OpenAPI spec and generated client are
unchanged. Sort stays omitzero: its "no sort" value is "none", not "", so
omitzero only omits the invalid unset state.
* fix(dashboardtypes): round-trip panel and dashboard links
`links` used omitempty (dropped an explicit []) and its element type was the
imported perses dashboard.Link, whose own fields tag name/tooltip/
renderVariables/targetBlank omitempty — so a link's false/"" were dropped
too, and a typed client read them back as null.
- Replicate dashboard.Link as a SigNoz Link type (same pattern as
ListVariableSpec/TextVariableSpec) with every field always serialized.
- Use ,omitzero on PanelSpec.Links and DashboardSpec.Links so an explicit
[] round-trips while an unset list stays omitted (never null).
Regenerate the OpenAPI spec and frontend client: the element schema is now
DashboardtypesLink (was the perses DashboardLink) and links is nullable.
Update the frontend consumers to the renamed type and coalesce the now
type-nullable spec.links (never null on the wire) at its two boundaries.
* test(dashboard): cover variable/display/link round-trip cases
Extend the v2 dashboard round-trip test with the spec-wide zero values this
PR fixes: a display description "", a text variable's constant false, a list
variable's customAllValue/capturingRegexp "", an explicit [] of panel links
that round-trips, a link whose own zero-valued fields (name/tooltip "",
renderVariables/targetBlank false) echo back, and a linkless panel whose
links stay omitted (never null).
* test(dashboard): accept null-or-absent for unset panel links
A panel with no links round-trips as "links": null rather than being
omitted (the panel serialization path differs from the query slices, which
omit). Both mean "no links" and neither drifts for a typed client, so assert
the value is None (null or absent) instead of strictly absent. The explicit
[] case still asserts a verbatim round-trip, which is the guarantee the fix
provides.
* fix(dashboardtypes): round-trip remaining zero-valued spec fields
Complete the dashboards-v2 create -> GET round-trip audit:
- DashboardSpec.Datasources: ,omitempty -> ,omitzero so an explicit {}
round-trips (omitempty dropped it) while an unset map stays omitted.
- DashboardV2 Image, DashboardSpec.Duration/RefreshInterval: drop ,omitempty
so an explicit "" round-trips (same class as Display.Description). The
server accepts "": DurationString.validate() returns nil for len 0, and
Image/Duration/RefreshInterval have no create-time validation, so a
GET-then-PUT of "" is not rejected.
Scalars carry no nullability (no spec change); the datasources map is now
nullable: true in the regenerated OpenAPI spec and client.
* test(dashboard): cover datasources/image/duration/refreshInterval round-trip
Extend the round-trip test with the spec-wide zero values just fixed: a
dashboard-level image "", spec duration/refreshInterval "", and an explicit
empty datasources {} that must echo back as {} (omitzero) rather than being
dropped.
* fix(querybuildertypesv5): omit unset metric enum fields on the wire
A metric builder query serialized empty strings for its enum fields
because omitempty has no effect on struct-backed valuer types:
"source":"", "aggregations":[{"temporality":"","timeAggregation":"","spaceAggregation":""}]
Those "" values are not members of the corresponding OpenAPI enums
(source=[meter], temporality=[delta,cumulative,unspecified], etc.), so a
typed client reading a rule back rejected it (create -> GET round-trip
drift; terraform-provider-signoz generate-config failed schema validation).
Tag Source/Temporality/TimeAggregation/SpaceAggregation with ,omitzero so
an unset value is dropped instead of emitted as an invalid "", matching the
existing convention (dashboardtypes Sort, telemetrytypes field keys). Valid
values still serialize. The OpenAPI spec regenerates byte-identical, which
confirms the enums were already correct.
* test(querybuildertypesv5): cover client-sent empty enum values
A client (e.g. terraform) may send explicit source:"" / temporality:"" for
an unset enum. Assert unmarshaling accepts them, normalizes to the zero
value, and re-marshaling drops them so the round-trip never echoes an
invalid "" back.
* fix(querybuildertypesv5): allow empty metric enum values in the spec
The server accepts and echoes back an unset source, temporality, and
timeAggregation for a metric query (a create -> GET returns "" for them), but
their OpenAPI enums omitted "". A typed client (terraform-provider-signoz
generate-config) therefore rejected the config generated for an imported rule.
Add "" as a valid member of the Source, Temporality, and TimeAggregation enums
so the spec matches what the server actually accepts and returns. spaceAggregation
is left unchanged: an empty value is rejected with 400 at creation (IsValid), so
it is never stored or echoed and "" must stay out of its enum.
Drop the earlier ,omitzero tags: these fields already always-serialize, so an
accepted "" round-trips faithfully instead of being silently dropped (silent
mutation is itself drift). source loses its no-op omitempty for the same reason.
Regenerate the OpenAPI spec and frontend client (both git-diff gated).
* test(querybuildertypesv5): assert accepted empty enums round-trip
Empty source/temporality/timeAggregation are echoed back (not dropped) and are
stable across marshal -> unmarshal -> marshal; spaceAggregation carries a valid
value since an empty one is 400'd at creation.
* test(querybuildertypesv5): merge and rename metric enum round-trip test
Fold the unmarshal-echo case into the table-driven marshal round-trip test
(its marshal -> unmarshal -> marshal check already covers the client-sends-""
path) and rename to TestQueryBuilderQuery_MetricAggregation_MarshalJSONEnumRoundTrip.
* style(metrictypes): drop explanatory comments on enum changes
Remove the comments added to Temporality/TimeAggregation Enum() and the metric
enum round-trip test case.
* style(telemetrytypes): drop explanatory comment on Source enum change
Remove the comment added to Source.Enum(), keeping the pre-existing doc/TODO.
The series lookup bounded registration rows with unix_milli < end while
the samples query uses <= end. The exporter writes registration rows
hour-floored, so a series first registered in the hour starting exactly
at end was invisible even though its samples were fetchable — queries
ending exactly on an hour boundary missed newly-registered series.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: convert key not found to warnings
* fix(logs): has-family body-only errors, not-found warnings, and condition-builder cleanup
- has/hasAny/hasAll/hasToken on a non-body key now return "supports only body
JSON search" (both modes); a not-found body path warns and queries the
underlying data instead of 400 (JSON mode)
- capture the two has-family 500s (hasToken separator/whitespace needle;
hasAny/hasAll quoted int >= 2^32) as regression tests
- use telemetrytypes.NewTelemetryFieldKey for derived keys (fresh identity-only
keys, no stale resolved metadata) instead of struct copies of the field key
- rename ConditionForKeys -> ConditionFor and inline conditionsForKeys into it
across the builders; rename private conditionFor -> conditionForResolvedKey
- move the trace_noise fixture from queriertraces/conftest.py to fixtures/traces.py
* fix: convert trace_noise fixture to util
#8563 worked around duplicate-labelset collisions (a label value going
empty re-fingerprints a series; empty-valued labels are dropped at read)
by appending a synthetic fingerprint label to every series and stripping
it from results. The label participates in evaluation before any strip
runs: without() grouped per fingerprint (duplicate identical-labeled
output series) and unaggregated vector matching never matched.
Resolve series identity at the adapter instead: drop empty-valued labels
at unmarshal, merge fingerprints that share the resulting labelset
(timestamp-ordered k-way merge, highest fingerprint wins equal
timestamps), and delete the injection and every RemoveExtraLabels strip.
Engine-semantic duplicates (e.g. rate over {__name__=~"a|b"}) still
error, matching upstream Prometheus.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The sub-1 branch's scale (10^(-order+n-1)) overflows to +Inf for
subnormal inputs (order below ~-308), and Inf/Inf turned a finite stored
value into NaN — which then serializes as the string "NaN". Same guard
as the >=1 branch got in #12151; adds the unit coverage for both.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboardtypes): accept threshold value of 0 on create
A NumberPanel/TimeSeries/Table threshold with `value: 0` (a legitimate
value the SigNoz UI emits by default) was rejected on create with
`dashboard_invalid_input` "Field validation for 'Value' failed on the
'required' tag".
go-playground/validator's `required` treats a numeric field equal to its
zero value as "missing", so `validate:"required"` on the float `Value`
wrongly rejected 0. Drop `validate:"required"` from `Value` on
ThresholdWithLabel and ComparisonThreshold; keep `required:"true"` since
the field is always present in the schema (0 is a valid present value, not
an absent one), so the OpenAPI/generated client are unaffected. `Color`
keeps both tags — an empty colour is genuinely invalid.
Drop the two "missing value" cases from TestValidateRequiredFields, which
asserted the removed invariant.
* fix(querybuildertypesv5): round-trip zero-valued query spec fields
A dashboard/alert query that sets a zero-valued field — `disabled: false`,
`legend: ""`, or an explicit empty `groupBy`/`order`/`selectFields`/etc. —
created fine but the GET response omitted it, so a typed client that echoes
what it sent (Terraform, SDKs, PUT-after-GET) read back `null`/absent and
reported drift. `,omitempty` dropped these zero values on the way out.
Fix the create -> GET asymmetry:
- Slice fields use `,omitzero` instead of `,omitempty`. `omitzero` omits a
nil slice (field never set stays absent) but keeps an explicit non-nil
`[]`, so an empty array round-trips as `[]` and there is no `null`
regression. Applied to groupBy, order, selectFields, aggregations,
functions, secondaryAggregations and function args across the builder,
formula, trace-operator and join specs, plus ListPanelSpec.selectFields.
- Scalars `disabled` (bool) and `legend` (string) drop the tag entirely;
`omitzero`/`omitempty` both suppress false/"", so the only way to
round-trip them is to always serialize.
Result types in resp.go keep `,omitempty` — they are server-computed and
never round-tripped. Regenerate docs/api/openapi.yml and the frontend
client: the omitzero slices are now `nullable: true` in the schema (never
null on the wire, but the generated types gain `| null`, which existing
consumers already handle via `?? []`).
* test(dashboard): round-trip serialization for zero-valued fields
Add a v2 dashboards integration test that creates one minimal dashboard
(stripped from SigNoz/dashboards cicd-perses.json) and asserts the
create -> GET round-trip preserves every zero-valued field the fix targets:
- threshold value 0 (ComparisonThreshold + ThresholdWithLabel) is accepted
on create and echoed back
- builder slices set to an explicit [] (groupBy/order/selectFields/functions)
round-trip as [], while a bare builder's unset slices stay absent (never
null) on read
- scalars disabled/legend always echo false/""
Table-driven: one equality table for round-tripped values and one absence
table for omitted slices.
* test(dashboard): fold round-trip test into 03_v2_dashboard
Move test_dashboard_v2_roundtrip_preserves_zero_values alongside the other
v2 dashboard tests (test_create_rejects_*, lifecycle, ...) instead of a
standalone file, with the dashboard payload inlined per this suite's style.
math.Round(val*multiplier)/multiplier overflows to +Inf for finite values
near math.MaxFloat64, turning a valid series value into JSON-unmarshalable
Inf downstream. Return the value unrounded when the scaled intermediate
overflows.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(authz): enable FGA for telemetry resources on v5 query_range
Authorize /api/v5/query_range and /preview at the telemetry-resource level,
derived from the request body:
- coretypes: ResourceWithID + ResourceExtractor as the resource-level analogue
of the id extractors; NewResolvedResourceWithID/NewResolvedResourceWithError;
telemetryresource selector regex widened to query-type selectors with up to
two hashed segments (metric name, where clause) or wildcards
- telemetrytypes: QueryRangeResources maps each query to its telemetry
resource (signal/source aware: audit-logs, meter-metrics) with a hierarchical
selector id (query_type/<hash(metric)>/<hash(where)>); PrefixSelector expands
the id into the grant ladder [exact, prefix/*..., *]
- handler: generic TelemetryResourceDef fans out an injected ResourceExtractor;
fails closed when extraction errors or resolves nothing
- audit: log and skip resolved resources that carry a resolution error
- querier routes: ViewAccess -> CheckResources with telemetry read scopes;
substitute_vars stays ViewAccess (no telemetry access)
- sqlmigration 099: backfill telemetry read tuples for existing orgs
(admin: logs/traces/metrics/audit-logs/meter-metrics; editor/viewer:
logs/traces/metrics)
* feat(authz): widen telemetry selector segments to 128 bits
64-bit truncation permits chosen-collision attacks at ~2^32 work; 128 bits
pushes this to 2^64. No hashed selector is persisted yet, so the change is
free.
* chore(docs): regenerate openapi spec with telemetry read scopes
* feat(telemetry): add where clause visitor
* refactor(telemetry): restructure normalizer file and quote bare values
* feat(authz): gate v5 query_range on service.name telemetry selectors
* feat(authz): encode telemetry grants as query-type qualified atom selectors
* feat(authz): move telemetry grant key to plaintext selector segment
* feat(authz): use escaped plaintext telemetry selectors with mechanical ladder
* revert(authz): restore transaction group diff in role update
* test(authz): add querierauthz integration suite for telemetry query_range gating
* test(authz): seed logs so service.name resolves in allowed querierauthz cases
* feat(authz): backfill telemetry read tuples for existing orgs
* chore(authz): reword empty composite query error message
* feat(authz): add meter metrics and audit logs to clickhouse sql
* Revert "feat(authz): add meter metrics and audit logs to clickhouse sql"
This reverts commit c9d870e0ee.
* feat(authz): grant meter-metrics to editor/viewer, keep clickhouse admin-only
* feat(authz): remove the audit logs from clickhouse check altogether until it's introduced
* feat(quick-filters): add field values hook
* feat(quick-filters): add support to handle indeterminate state of checkbox
* feat(quick-filters): add checkbox header v2
* feat(quick-filters): add item rules to represent the states of checkbox
* feat(quick-filters): add checkbox row v2
* feat(quick-filters): add sectioned values hook to correctly calculate checkboxes to render
* feat(quick-filters): add hook to get existingQuery for field values
* feat(quick-filters): add checkbox filter v2
* test(quick-filters): add tests for checkbox filter v2
* feat(quick-filters): add flag to enable v2
* feat(infra-monitoring): enable quick filters v2
* fix(infra-monitoring): change the prefix of metric namespaces
* fix(checkbox-v2): update to improve ux
* fix(quick-filters): move from flat list to sections
* fix(quick-filters): render related/all values when searching
* Revert "fix(infra-monitoring): change the prefix of metric namespaces"
This reverts commit 176dc6d3bc.
* Revert "feat(infra-monitoring): enable quick filters v2"
This reverts commit 8902a157c1.
* fix(pr): address comments
* feat(infra-monitoring-v2): enable use new quick filters
* fix(pr): cleanup old file
* fix(quick-filters): avoid flick of old selected value on related value
* refactor(data-export): extract the withUnit header helper
Both the timeseries and the upcoming table serializers append units to headers (and skip display-only ids like 'short'/'none') — move the helper to its own module.
* feat(data-export): add generic table-model serializer
exportTableData serializes any prepared antd-style table model ({name, key, isValueColumn} columns + record rows) into a SerializedTable — raw values in display order, units on value columns only, blanks for missing cells. Surface-agnostic: QueryTable-based tables, dashboard tables and plain antd tables all adapt in a line or two.
* feat(data-export): add scalar queryRange serializer
exportScalarData takes the queryRange response object (formatForWeb webTables payload) + the builder query and serializes via createTableColumnsFromQuery — the exact preparer QueryTable renders from — so exports inherit the on-screen merge, naming and column order 1:1.
* feat(data-export): dispatch client exports from the queryRange response
useClientExport now takes the queryRange response object views already hold and picks the serializer from what it carries: rawV5Response (time_series) or the formatForWeb scalar payload (tables) — mounts pass their data without choosing serializers. TimeseriesExportMenu generalizes into components/ExportMenu with the same uniform inputs, so the timeseries views and the table tabs share one menu.
* feat(explorer): enable table export in Logs and Traces table tabs
Mounts ExportMenu on the Logs and Traces Table tabs, feeding the same query the rendered table uses (stagedQuery fallback parity) — exports match the on-screen table's merge, naming and column order. Header rows are class-driven, right-aligned, gated on data presence.
* test(data-export): assert scalar export filename via the naming helper
Same review cleanup as the foundation PR: frozen clock + delegation to getTimestampedFileName instead of a duplicated format regex.
* refactor(alerts-v2): extract alert condition normalizers to a leaf module
normalizeOperator/normalizeMatchType move from CreateAlertV2/utils.tsx into a
dependency-free context/conditionNormalizers.ts (re-exported from utils for existing
callers), so the CreateAlert context can consume them without an index<->utils cycle.
* feat(alerts-v2): apply alert-condition prefill declaratively from the URL
The CreateAlertV2 context now resolves a declarative prefill from the URL
(resolveUrlAlertPrefill) and applies each field iff present, instead of branching on
the request source. Producers own their semantics: ingestion settings now explicitly
declares matchType=in_total + evaluationWindowPreset=meter, so adding a new producer
needs no consumer change.
* feat(dashboards-v2): pre-populate alert condition from panel Reduce To + thresholds (#5291)
Creating an alert from a V2 panel now seeds the condition: Reduce To -> occurrence
type (sum->in total, avg->on average; Value panel only), and the highest-danger
threshold (Red>Orange>Green>Blue) -> operator, target value, and unit. Formula panels
apply the reduce only when all queries agree. The seed rides the /alerts/new URL and is
applied by the declarative CreateAlertV2 consumer.
* fix(ruletypes): always serialize notificationSettings.usePolicy
usePolicy is a plain bool tagged omitempty, so a false value is dropped
from the GET response. Clients that pin usePolicy to false read it back as
absent/null. Drop omitempty (matching Renotify.Enabled) so false always
serializes and round-trips.
* test(ruletypes): assert usePolicy round-trips in minimal read shape
usePolicy now always serializes, so the minimal read shape includes it with a false value rather than omitting it.
* fix(ruletypes): always serialize notificationSettings.groupBy
Like usePolicy, groupBy dropped its omitempty so an explicitly-set empty value round-trips instead of reading back as absent. A nil groupBy now serializes as null.
* fix(ruletypes): use omitzero for notificationSettings.groupBy
omitzero omits groupBy only when nil (unset) while preserving an explicitly-set empty array as [], so unset and empty stay distinguishable instead of every response carrying groupBy: null. Matches the sibling newGroupEvalDelay tag.
* fix(ruletypes): use omitzero for renotify.alertStates
Symmetric to groupBy: omitzero omits alertStates only when nil (unset renotify configs stay absent, no regression) while echoing an explicitly-set empty array as [].
* chore(ruletypes): regenerate openapi spec and api client
groupBy and renotify.alertStates switched to omitzero, so the generated spec marks both slices nullable and the orval client types them as [] | null.
* fix(llm-pricing): constrain currency dropdown width, drop tab URL param
- Currency SelectSimple stretched to fill the filters bar; give it a fixed
160px width (min-width couldn't cap the trigger).
- Model costs is the only enabled tab for now, so use Tabs defaultValue
instead of a URL-backed param. Removes the nuqs tab state plus the now-unused
TAB_KEYS/TAB_QUERY_KEY constants and TabKey type.
* chore: self review changes
* fix: add skeleton loading
* refactor: self review changes
* refactor: initial prop
* fix: update styling
* fix: add comments in utils
* feat(llm-pricing): add model cost drawer and wire into listing page
* fix(llm-pricing): restrict pricing management to admins
Align the frontend write gate with the backend, which protects the
LLM pricing create/update/delete endpoints with AdminAccess (admin
only). Previously manage_llm_pricing allowed EDITOR/AUTHOR, so those
roles saw the Add/Save affordances but their writes were rejected with
a 403. Also removes the AUTHOR entry, which could never reach the page
(the route gate excludes it).
* fix(llm-pricing): read-only drawer shows View title, hides source picker
Non-managers open the drawer in view mode (write APIs are Admin-only), so:
- the heading reads "View model cost" instead of "Edit model cost"
- the Source (auto vs. override) picker is hidden, since switching source is
a manager-only action with nothing actionable for a viewer.
* refactor: form in edit / add modal
* chore: update color tokens
* fix: add error handling
* chore: update more self review changes
* chore: self review changes
* chore: self review changes
* fix: minor grammer thing
* fix: route thing
* feat(llm-attribute-mapping): add attribute mapping foundation (route, permission, page shell)
* feat(llm-attribute-mapping): read-only groups and mappers listing
* feat(llm-attribute-mapping): group create/edit/delete via drawer with diff-save
* fix: css styling
* style(llm-attribute-mapping): use design-token css variables for spacing and typography
* fix(llm-attribute-mapping): wrap long condition keys so the context badge isn't clipped
* fix(llm-attribute-mapping): unlock filters column shrink so long keys wrap within the cell
* fix: text overflow in attribute mapping
* fix: fetch data only when tab is open
* feat: add attribute mapping tab
* fix: update col count
* chore: review comments
* fix: input
* fix: update logic
* fix: update drawer
* feat: update draft and mapper
* refactor: migrate to css moduel
* refactor: migrate to css module
* refactor: migrate to css module
* refactor: css module
* feat(llm-attribute-mapping): read-only listing on CSS modules [2/5]
Rebase the listing slice onto the foundation's CSS-module refactor
(which deleted the global stylesheet) and migrate it accordingly:
- Merge listing styles into LLMObservabilityAttributeMapping.module.scss
(groups/mappers tables, source chips, index badge, error/footer).
- Convert all listing components from global BEM classNames to
styles.* module access; drop dead/style-less classes (am-table,
am-row-actions, am-add-row, *_edited, mappers-table__error).
- Adopt theme-aware semantic tokens (--l2/l3-*, --accent-primary,
--callout-error-*) in place of --bg-* primitives.
* refactor(llm-attribute-mapping): migrate styles to CSS modules
* refactor: migrate to tanstack table
* docs: clarify price precision comment
* chore: remove comment
* feat(llm-attribute-mapping): add attribute mapping foundation (route, permission, page shell)
* fix: css styling
* refactor: css module
* feat(llm-attribute-mapping): read-only listing on CSS modules [2/5]
Rebase the listing slice onto the foundation's CSS-module refactor
(which deleted the global stylesheet) and migrate it accordingly:
- Merge listing styles into LLMObservabilityAttributeMapping.module.scss
(groups/mappers tables, source chips, index badge, error/footer).
- Convert all listing components from global BEM classNames to
styles.* module access; drop dead/style-less classes (am-table,
am-row-actions, am-add-row, *_edited, mappers-table__error).
- Adopt theme-aware semantic tokens (--l2/l3-*, --accent-primary,
--callout-error-*) in place of --bg-* primitives.
* chore: migrate tanstack table
* chore(llm-attribute-mapping): remove orphaned IndexBadge
The TanStack mappers table dropped the leading index column, leaving
IndexBadge and its .indexBadge style unused. Remove both.
* chore: remove comment
* fix: disable isDirty in case of llm pricing
* refactor: number
* feat: add search , dropdown and flag
* feat: feature flag on entire route and add mode costs tabs
* fix: add isFetchingFeatureFlags
* chore: update flag
* refactor: shell
* fix: add key to route
* feat: add flags
* chore: additional refactor
* chore: add commet in utis
* chore: self review changes
* refactor: types and other things
* refactor: types and other things
* chore: add disable on source id
* empty commit
* chore: empty commit
* fix: add demo side nav on sidenav
* chore: remove demo side nav
* refactor: update routes
* chore: remove usd selector for now
* fix: layout shift
* refactor: styles
* refactor: typography component
* refactor: more changes
* refactor: typograhy
* refactor(llm-pricing): break model-cost drawer into per-component files + tokens
Apply the CSS-module/component conventions to the drawer that came from
drawer-3:
- Move the drawer under ModelCostTabPanel/components/ModelCostDrawer/ to mirror
the ModelCostsTable structure
- Split the single 395-LOC ModelCostDrawer.module.scss into per-component
co-located modules; cross-component selectors live in shared.module.scss and
are pulled in via CSS-modules `composes`
- shared.module.scss is a composes target (parsed as plain CSS), so it is kept
flat with block comments — no SCSS nesting or // comments
- Use --text-vanilla-* (not --bg-vanilla-*) for text colors, matching the
listing code
* refactor: more changes
* refactor: styling and components
* refactor: styling and components
* chore: add a tooltip on hover
* feat: add delete confirm modal
* fix: update title
* refactor: css variables
* refactor: use signoz button and minor css update
* chore: sync table
* chore: remove extra comment
* chore: use typograpgy test in table config
* fix: minior issues
* fix: llm pricing listing
* refactor: remove extra classes
* refactor: side nav changes
* fix: update missing styles
* chore: update edit and delete options
* chore: remove extra comment
* chore: revert env changes
* chore: add enable check
* chore: remove divider
* refactor: use delete confirm dialog
* chore: remove scss file
* feat: move ui to easily accessable tabs
* feat: update test cases
* chore: update text
* chore: self review changes
* chore: self review refactor
* chore: self review changes
* chore: remove worktree
* chore: revert env.ts
* chore: add attribute mapping foundation
* chore: update ui and add animation
* refactor: components update
* chore: typography changes
* chore: typography changes
* chore: use badge
* refactor: basic components
* chore: remove hardcoded value
* chore: add comments & tests
* chore: update mapper
* chore: update test
* chore: minior ux update
* chore: lodash isEqual
* chore: use lodash
* chore: update save draft
* chore: update env.ts
* chore: update tests
* chore: self review changes
* chore: update test cases
* chore: remove extra comments
* refactor(llm-pricing): share toast copy via constants
* chore: use constants
* chore: redclared constants
* chore: update test cases
* chore: remove unused component
* chore: update types
* chore: update design
* chore: update ui
* refactor: minor things
* chore: break down thingsinto comps
* chore: update files
* fix: update mapping
* chore: remove draft logic no need for now
* chore: more refactor
* chore: remove comments
* chore: refactor route
* chore: update query refech on mount
* chore: self review changes
* chore: update skeleton
* refactor: code
* chore: sync up with group mappers
* chore: only 1 mapper row at a time
* refactor: code
* refactor: eslint disable
* chore: update selector
* chore: sort
* refactor: review changes
* chore: use refetch group
* feat(llm-attribute-mapping): mapper drawer + persistMappers [4/5] (#11781)
* feat(llm-attribute-mapping): mapper drawer + persistMappers [4/5]
Core of the mapper drawer work: the MapperFormDrawer + SourceAttributeRow
UI, persistMappers store logic, mapper row/actions, and supporting
utils/types/saveDraft changes.
Removes the barrel (index.ts) files introduced by this PR
(MapperFormDrawer, MapperActionsMenu, SourceAttributeRow) and imports
those modules directly instead.
Tests, public locales, the conditions tooltip, and the KeySearchInput
relocation follow in a stacked follow-up PR.
* test(llm-attribute-mapping): tooltip, locales, tests + drop barrels [4/5 follow-up]
Follow-up to the mapper drawer core PR. Contains:
- AttributeMappingsTab + LLMObservabilityAttributeMapping test suites
- public locale (titles.json) updates
- the group ConditionsTooltip and its GroupHeader wiring
- relocation of KeySearchInput to the shared components/ dir
- removal of the remaining barrel (index.ts) files across the
AttributeMapping feature, importing each module directly
* chore: remove comments
* chore: reduce width
* fix: no refetch on open
* chore: add discard dialog
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
* chore: remove reduced motion
* chore: self review changes
* chore: c should be capital
* chore: review changes
* refactor: sync server store
* chore: update tests
* Revert "chore: update tests"
This reverts commit 5f39c910df.
* Revert "refactor: sync server store"
This reverts commit b92f902d9f.
* chore: rename to editor
* chore: resolve review comments
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
* chore(dashboard-v2): drop self-explanatory comments from the variable subsystem
Remove comments that restate a well-named symbol or narrate obvious steps, and
trim verbose JSDoc to the non-obvious "why". No behavior change.
* refactor(dashboard-v2): organize VariablesBar into components/ hooks/ utils/
Split the flat VariablesBar directory into components/ (each component in its
own folder, co-locating a component-specific .module.scss), hooks/ (all use*
including useVariableOptions, moved out of selectors/), and utils/ (pure
modules). The bar's shared cross-cutting classes and .control stay in the root
VariablesBar.module.scss. Import paths updated across the subsystem; no
behavior change. Also updates a stale URL assertion in the seed test left over
from the store-source-of-truth change.
* refactor(dashboard-v2): organize DashboardSettings/Variables into components/ hooks/ utils/
Move the loose files out of the Variables root: pure modules into utils/
(rename variableDependencies -> variableCycleDetection to end the name clash
with VariablesBar's graph engine), useSaveVariables into hooks/, and VariableRow
and VariablesList into their own component folders with co-located .module.scss.
The shared model, adapters and types stay at the root as the folder's public
API. Import paths updated across the subsystem; no behavior change.
* refactor(dashboard-v2): factor the variable tooltip ref lists into one directional component
Extract TooltipRefSection so "Depends on" and "Used by" share one component,
differing only by color and an up/down arrow that depicts the dependency
direction (up = parents this variable depends on, down = children that use it).
* refactor(dashboard-v2): stack the variable name above its control
Move each variable's name into a notch label above the control: fixed-width
columns, name truncated with a hover title and a spaced/centered info icon,
single-line multi-select, ~33px control height, and a visible dropdown caret.
Nudge the toolbar spacing so the time selector aligns with the shorter bar.
Also render the impact dialog's "still references" warning via Typography
color="warning" instead of a bespoke CSS class.
* fix: top margin
* feat(dashboard-v2): single default resolver for variable selection
One source of truth for a variable's value (seed → reconcile → payload), shared by the bar, the fetch gate and the query payload so they can't disagree. Precedence stays URL → store → default; ALL materializes once, without an extra round-trip.
* feat(dashboard-v2): dependency-ordered, value-gated variable fetch engine
Query roots + dynamics fetch immediately; query children unblock only once their parents hold a committed value, so a chain fetches each variable once. The cycle is idempotent per (dashboard·order·time) and resets on page unmount, so a re-mount or editor return can't double it or inherit stale state.
* refactor(dashboard-v2): consolidate variable selectors; add bar tooltip + loading bar
Fold the per-type Query/Dynamic/Custom selectors into one VariableValueControl backed by useVariableOptions. Add a hover tooltip showing a variable's dependencies (Depends on / Used by) and a loading bar flush along the control's bottom edge; refine name/dependency colors to design-system tokens.
* feat(dashboard-v2): hold panels in loading until referenced variables resolve
A panel waits until every QUERY/DYNAMIC variable it references is ready — a concrete pick and a DYNAMIC ALL are ready immediately, an unselected value or a QUERY ALL waits until it resolves. The wait folds into the panel's loading flags (a disabled query reports neither), so it never flashes "no data". Only referenced variables re-key the cache.
* feat(dashboard-v2): rename/delete variable impact dialog
Renaming or deleting a referenced variable is blocked behind a dialog listing every usage (QB / PromQL / ClickHouse panels and other variable defs), with a current → resulting preview, per-usage selection, and inline edits; applied as one JSON-Patch.
* fix(dashboard-v2): don't flash ALL on a multi-select while its options load
CustomMultiSelect derives "all selected" from options + the current value; with options still empty a concrete selection reads as covering everything. Only enable ALL detection once options have loaded.
* refactor(dashboard-v2): make the store the source of truth for variable selection
The persisted store (localStorage) is now the sole source of truth for runtime
variable values. Stop writing ?variables= on every selection change; the URL
param is read once on load (a share link) to seed the store and then dropped,
so dashboard navigation and drilldown no longer round-trip through the URL. The
panel editor reads the selection from the store (a lagging URL snapshot could
clobber a just-changed value). Drops the now-unused withVariablesSearch.
* refactor(dashboard-v2): address review feedback on the variable subsystem
- consolidate the default resolver: one configuredDefault normalizer shared by
resolveDefaultSelection and configuredDefaultValue, plus a textDefault helper
for TEXT (drops the dead textValue/empty-string fallbacks)
- reuse computeVariableDependencies for the bar tooltip's depends-on / used-by
- split the fetch machinery out of useVariableOptions into useFetchedVariableOptions
- extract useVariableListActions from the variables settings page
- reuse removeVariableFromExpression for delete-clause removal (handles dangling
syntax / parentheses) and drop the local removeVariableReferenceClause
- grey the impact dialog's Current field on uncheck; comment + spacing nits;
guard useIsPanelWaitingOnVariable so it reads variableTypes without optional chaining
- fix the seed test's read-once URL assertion
* feat(dashboard-v2): new-panel editor route + query-preserving seed
Add the /dashboard/:id/panel/new route helpers (buildExportPanelLink,
parseNewPanelKind, layoutIndex) and buildNewPanelSeed, which converts an
exported explorer compositeQuery into a seeded panel.
Coerces a builder-only kind (List) to a query-compatible one (PromQL ->
TimeSeries, ClickHouse -> Table) so a non-builder query is preserved rather
than dropped. PanelEditorPage consumes the effective kind; new List panels
seed columns from the query's resolved signal.
* feat(dashboard-v2): flag-aware export-to-dashboard hooks
Add the hooks shared by the explorer "Add to dashboard" flow:
- useGetExportToDashboardLink: builds the V2 panel-editor link (or the V1
new-widget link) based on the use_dashboard_v2 flag.
- useExportDashboards: flag-aware, search-filtered dashboard source. V2
searches server-side via the name-contains filter DSL (debounced); V1
filters its already-complete list in memory.
- useCreateExportDashboard: flag-aware "create a new dashboard" - V2 via the
Perses createDashboardV2, V1 via the legacy create - normalized to Dashboard.
* feat(dashboard-v2): rebuild "Add to dashboard" export dialog
Replace the antd Modal-based ExportPanel with a @signozhq/ui DialogWrapper
dialog driven by the export hooks. The picker searches server-side and pins the
selected dashboard as an option so it survives a narrowing search; "New
dashboard" creates via the flag-aware hook and navigates to the panel editor.
Removes the old index.tsx / styles.ts.
* feat(dashboard-v2): wire explorers to flag-aware export
Point the Logs/Traces/Metrics explorer "Add to dashboard" actions at
useGetExportToDashboardLink, so an exported query lands in the V2 panel editor
when use_dashboard_v2 is on (the V1 new-widget link otherwise). ExplorerOptions
now renders the rebuilt ExportPanel dialog directly instead of wrapping it in an
antd Modal.
* chore: fixed failing tests
* refactor(dashboard-v2): address explorer-export review feedback
- rename isQueryTypeSupported → isQueryTypeSupportedByPanelKind for clarity
- buildExportPanelLink returns null (no silent TimeSeries default) when a
panel type has no V2 kind; explorers guard navigation on the null link
- introduce a neutral ExportDashboard {id,title} for the picker instead of
reusing the V1 Dashboard entity with faked fields
- note why an exported Query's single unit doesn't seed columnUnits
* chore: pr review changes
* refactor(infrastructure-monitoring-v2): remove pod phase and add pod status/counts
* fix(column-header): align to left
* fix(cell-value): ensure tooltip is correctly aligned
* fix(entity-traces): use - instead of N/A
* test(entity-traces): fix broken test
* fix(k8s-base-details): do not allow untoggle tabs
* feat(constants): add base queries for all workloads
Ref: https://github.com/SigNoz/engineering-pod/issues/4032#issuecomment-4893103716
* feat(daemon-sets): add pod metrics tab
* feat(deployments): add pod metrics tab
* feat(jobs): add pod metrics tab
* feat(namespaces): add pod metrics tab
* feat(statefulsets): add pod metrics tab
* fix(k8s-base-details): do not keep selected invalid tab
* fix(pod-metrics): add new badge
* Revert "fix(pod-metrics): add new badge"
This reverts commit 09b8624f52.
* chore(k8s-base-details): mark tab as possible null
* chore: add integration tests for metrics under reduction - query part
* chore: add to ci
* chore: add todos
* chore: update tests
* chore: trigger build
* chore: address review comments
* chore: undo the changes to tests/fixtures/http.py
* chore: remove __normalized and add fast path
* chore: fix unit tests
* chore: update test
* chore: sunset 25.5.6, update migrator version, and remove version arg
* chore: also update devenv
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(entity-count): add base structure to show count
* feat(entity-count): add count for clusters
* feat(entity-count): add count for namespaces
* chore(infra-monitoring): move component to be under components folder
* fix(pr): address comments
* refactor(data-export): extract the withUnit header helper
Both the timeseries and the upcoming table serializers append units to headers (and skip display-only ids like 'short'/'none') — move the helper to its own module.
* feat(data-export): add generic table-model serializer
exportTableData serializes any prepared antd-style table model ({name, key, isValueColumn} columns + record rows) into a SerializedTable — raw values in display order, units on value columns only, blanks for missing cells. Surface-agnostic: QueryTable-based tables, dashboard tables and plain antd tables all adapt in a line or two.
* feat(data-export): add scalar queryRange serializer
exportScalarData takes the queryRange response object (formatForWeb webTables payload) + the builder query and serializes via createTableColumnsFromQuery — the exact preparer QueryTable renders from — so exports inherit the on-screen merge, naming and column order 1:1.
* feat(tanstack): add support for itemKey be object
* feat(infra-monitoring): add base structure for multiple selected items
* refactor(infra-monitoring): add cluster for namespaces, cluster/namespace for volumes + update other entities
* refactor(infra-monitoring): add cluster + namespace for statefulsets
* refactor(infra-monitoring): add cluster + namespace for jobs
* refactor(infra-monitoring): add cluster + namespace for deployments
* refactor(infra-monitoring): add cluster + namespace for daemonsets
* test(infra-monitoring): add new tests for changes of k8s base list
* fix(pr): address comments
* fix(rules): scope alert rule store operations by org
The rule store predicates filtered on the rule id only, so on multi-org
deployments (Cloud/EE/multi-org self-hosted, where orgs share the
instance via the noop sharder) an authenticated user could read, edit or
delete another org's alert rules by supplying the target rule UUID.
ViewAccess/EditAccess only check the caller's own-org role, never the
resource's org, so nothing enforced tenant isolation on the rule itself.
Enforce org scoping at the store layer, which is the durable fix:
- GetStoredRule and DeleteRule now take the caller's orgID and add
`org_id = ?` to their predicates.
- EditRule adds `org_id = ?` (from the model, which is now always
sourced from an org-scoped read).
- The manager passes claims.OrgID on every by-id path; GetRule now
derives the org from claims (it previously fetched by id alone).
Cross-org ids now resolve to NotFound instead of leaking or mutating
another org's rule. Single-org OSS instances are unaffected.
CWE-639 (authorization bypass through user-controlled key).
* refactor(rules): derive claims org id with valuer.MustNewUUID
Claims are pre-validated by the auth middleware, so the NewUUID error
branch is dead code. Follow the handler convention (docs/contributing/
go/handler.md) and use the Must constructor on the by-id rule paths.
* fix: set correct opapi response model for span mapper list
* fix: change group_id to groupId in response
* fix: format properly
* fix: update fixtures
* chore: support non-dsl search in dashboards v2 list apis
* chore: move free text compilation to visitor
* chore: shorten comments
* fix: support both plain text and dsl together
* fix: ensure empty description dashboards are not excluded in NOT checks
Panel visibility was latched (isObserverOnce), so once every panel had
scrolled into view they all stayed query-enabled. A time change or
auto-refresh re-keys every panel's query, refetching all of them at once.
Track the live viewport instead so off-screen panels stay query-disabled
and skip the refetch; they fetch lazily when scrolled back in. Add
staleTime: Infinity so re-entering an unchanged window serves cache rather
than thrash-refetching (key changes and manual refetch still run).
* feat: add functional unique index
* chore: add tag migration
* chore: add failing scratch test (to discuss)
* chore: fetch expressions as well in getindices call
* fix: remove tag unique index (will be added in a separate PR)
* fix: remove tag unique index (will be added in a separate PR)
* chore: remove temporary tests
* chore: add functional unique index for tags
* fix: go lint fix
* fix: go lint fix
* chore: better comment for unique index
* test: add test for case insensitive expression equality
* test: add equality test for columns with quotes and capital letters
* test: add test for quoted columns
* chore: add separate type for unique indices with expressions
* fix: update migration file to use correct index
* test: add test for postgres provider
* test: add test for postgres provider
* fix: remove round trip tests
* fix(context-menu): keep overlay clickable when opened inside a modal
Portal the popover to body and set pointer-events:auto so a modal's dialog/backdrop can't trap it.
* fix(dashboard-v2): center scrolled-to panels and sections
Default the scroll block to 'center' so a targeted panel lands mid-viewport.
* fix(dashboard-v2): make the view modal refresh button secondary
Use outlined/secondary so refresh doesn't compete with the primary action.
* fix(dashboard-v2): fetch only on-screen panels on dashboard load
RGL unfolds panels from the top-left origin on mount, briefly piling them into the viewport.
The latched observer then marked all visible; suppress the item transition for the first frame.
* chore: readded entry animation
* feat(data-export): surface raw V5 response and legend map from GetMetricQueryRange
Keeps the pre-conversion V5 queryRange response and the per-query legend map on the returned MetricQueryRangeSuccessResponse so explorer views can feed client-side export without a refetch. Additive optional fields; the legacy V3 render path is untouched. One chokepoint covers Logs, Traces and Metrics.
* fix(data-export): omit display-only format ids from export headers
'short'/'none' are Grafana-style display formats, not physical units — meaningful on a chart axis but misleading as 'value (short)' in a CSV header. Real units (ms, bytes, ...) are unchanged.
* feat(data-export): timeseries export menu and opt-in TimeSeriesView header
New TimeseriesExportMenu (csv/jsonl, LONG shape default; same look as DownloadOptionsMenu, which stays raw/list-only) rendered from a new opt-in TimeSeriesView header row: unit selector left (onYAxisUnitChange — rendering only, unit state stays with each view), download right (allowExport, needs the surfaced raw V5). Both default off, so views opt in explicitly. Includes gating + menu tests.
* feat(explorer): enable timeseries export in Logs, Traces and Metrics
Logs and Traces pass allowExport + onYAxisUnitChange and drop their external unit-filter headers (one header row: unit left, download right). Metrics passes allowExport only, per chart — its YAxisUnitSelector stays screen-level (persists to the metric, spans N charts). convertDataValueToMs now converts the raw V5 tree too, so Traces duration exports carry the ms values the chart shows instead of raw nanoseconds.
* refactor(data-export): use @signozhq/ui components in the export popover
Review feedback: swap antd Button/Popover/Tooltip for the design-system equivalents (radix-style Popover with controlled open, TooltipSimple, Button with native loading). Styles restructured for the DS DOM with periscope font tokens; test renders via tests/test-utils for the TooltipProvider.
* chore: added types and open api spec changes
* chore: added method to calculate reason
* chore: per group pod status counts with req metric checks method added
* chore: wired up pod status counts
* chore: pod restarts type added
* chore: added restart counts for the group
* chore: bug in query fix
* chore: onboarding API changes
* chore: integration tests added
* chore: added podcountsbyphase in other entities
* chore: added pod status counts for other entities
* chore: added integration tests for other entities
* chore: added checks api changes for other entities
* chore: rearrangement
* chore: removed succeeded status and mark it as completed
* chore: query beautified
* chore: corrected metrics list for metadata lookup
* chore: removed dead constants
* chore: goroutines for ListHosts
* chore: goroutines for ListPods
* chore: goroutines for ListNodes
* chore: goroutines for ListNamespaces
* chore: goroutines for ListClusters
* chore: goroutines for ListDeployments
* chore: goroutines for ListStatefulsets
* chore: added goroutines for ListStatefulsets, ListJobs and ListDaemonsets
* chore: added function
* chore: added struct changes
* chore: added count attr keys
* chore: wired counts to the response fields
* chore: regenerated API spec
* chore: merged main, resolved conflicts
* chore: nodes count surfacing
* chore: integration tests added
* fix: use tuple mapping to uniquely identify attrs:
* chore: integration tests update
* feat(dashboard-v2): stage-aware DSL autocomplete engine for the list filter
* feat(dashboard-v2): single query-string list-filter state with reflect/splice
* feat(dashboard-v2): drive saved/built-in views off the query string
* feat(dashboard-v2): CodeMirror query box with DSL autocomplete popup
* feat(dashboard-v2): antd Created-by/Updated filter selects, drop the Tags dropdown
* feat(dashboard-v2): wire the DSL filter into the dashboards list (sticky header, pagination)
* fix: small css elevation
* refactor(dashboard-v2): reuse shared operator constants in the DSL grammar
Build STRING_OPS / OPERATOR_MATRIX / VALUELESS_OPERATORS from the shared OPERATORS + negateOperator (constants/antlrQueryConstants) instead of hardcoded strings; drop a stale lint-disable in FilterChips. Addresses review comments.
* fix(dashboard-v2): wrap IN/NOT IN autocomplete values in a bracketed list
Picking a value for an IN/NOT IN clause in the DSL search box inserted a
bare literal ('x') instead of a list (['x']). The value stage now wraps a
fresh pick in a closed bracketed list, appends subsequent picks inside the
existing brackets (deduping already-entered values), and keeps the caret
inside the list so multi-select continues.
---------
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
* feat(llm-pricing): add model pricing foundation (route, permission, page shell)
* feat(llm-pricing): add listing page and table
* chore(llm-pricing): drop search + source filters from list request
The list API does not honour the q (search) and source params yet, so
the controls did nothing. Remove the search input and source dropdown
along with the params we sent, and trim useModelPricingFilters to the
URL-backed page state that pagination still needs. Currency dropdown,
tabs, table and pagination are unchanged. Filters will return once the
backend supports them.
* refactor(llm-pricing): extract getRelativeTime helper in utils
Pull the relative-time formatting out of getRelativeLastSeen into a
small local getRelativeTime helper. Kept feature-local (not in the
shared utils/timeUtils) so the LLM pricing module owns its own dayjs
config; the local relativeTime extend stays for test self-sufficiency.
* refactor(llm-pricing): drop dead NaN guard in formatPricePerMillion
Pricing fields are typed as required numbers and JSON can't carry NaN,
so Number.isNaN was unreachable. Keep the null/undefined guard as API
defensiveness (toFixed on a missing value would crash the row). Also
trims the now-redundant dayjs.extend comment.
* refactor(llm-pricing): centralize constants and shared types
Extract PAGE_SIZE, PAGE_KEY, COLUMN_COUNT and CURRENCY_OPTIONS into a
new constants.ts, and move the ModelPricingFilters contract into
types.ts. Component prop interfaces stay colocated with their
components, matching the convention in the drawer PR.
* refactor(llm-pricing): use nuqs for list pagination URL state
Replace the hand-rolled useHistory + URLSearchParams plumbing in
useModelPricingFilters with nuqs useQueryState, matching the convention
used by the dashboards, alerts and k8s list pages. Behaviour is
unchanged: parseAsInteger.withDefault(1) keeps ?page=1 out of the URL
and history:'replace' avoids polluting the back-stack.
* refactor(llm-pricing): inline pagination, drop useModelPricingFilters
The hook had shrunk to a one-line nuqs wrapper after search/source were
removed, so inline the useQueryState call into the container and remove
the hook file plus the now-unused ModelPricingFilters type. When the
filters return (once the API honours them) they can move back into a
dedicated hook.
* feat(llm-pricing): disable currency selector (USD-only for now)
Only USD is priced today, so render the currency SelectSimple in a
disabled state pinned to USD. A disabled select can't fire onChange, so
the currency useState is dead — drop it (and the now-unused useState
import).
* refactor(llm-pricing): render model costs inside its tab + tab URL param
The listing was rendered outside the Tabs, so the tab was decorative.
Move all model-cost content (currency control, list query, table,
pagination, footer) into a ModelCostsTab component rendered as the
'Model costs' tab's children, and drive the active tab from a 'tab' URL
query param (nuqs). The container is now just the page shell. Unpriced
models stays a disabled placeholder for a later PR.
* style(llm-pricing): target @signozhq table slots, drop dead antd/leftover rules
The component uses @signozhq/ui Table/Tabs (Radix-based), not antd, so the
.ant-table-* and .ant-tabs-nav selectors never matched — the intended
uppercase/muted header styling wasn't applied. Retarget header/cell rules to
[data-slot='table-head'|'table-cell'] (no !important needed). Also remove dead
rules left over from the removed search/source/add UI (.filters-bar__search,
__source, __add, .page-header__actions) and the unused .source-badge--auto/
--override modifiers.
* fix(llm-pricing): constrain currency dropdown width, drop tab URL param
- Currency SelectSimple stretched to fill the filters bar; give it a fixed
160px width (min-width couldn't cap the trigger).
- Model costs is the only enabled tab for now, so use Tabs defaultValue
instead of a URL-backed param. Removes the nuqs tab state plus the now-unused
TAB_KEYS/TAB_QUERY_KEY constants and TabKey type.
* chore: self review changes
* fix: add skeleton loading
* refactor: self review changes
* refactor: initial prop
* fix: update styling
* fix: add comments in utils
* feat(llm-pricing): add model cost drawer and wire into listing page
* fix(llm-pricing): restrict pricing management to admins
Align the frontend write gate with the backend, which protects the
LLM pricing create/update/delete endpoints with AdminAccess (admin
only). Previously manage_llm_pricing allowed EDITOR/AUTHOR, so those
roles saw the Add/Save affordances but their writes were rejected with
a 403. Also removes the AUTHOR entry, which could never reach the page
(the route gate excludes it).
* fix(llm-pricing): read-only drawer shows View title, hides source picker
Non-managers open the drawer in view mode (write APIs are Admin-only), so:
- the heading reads "View model cost" instead of "Edit model cost"
- the Source (auto vs. override) picker is hidden, since switching source is
a manager-only action with nothing actionable for a viewer.
* refactor: form in edit / add modal
* chore: update color tokens
* fix: add error handling
* chore: update more self review changes
* chore: self review changes
* chore: self review changes
* fix: minor grammer thing
* fix: route thing
* refactor: migrate to css moduel
* refactor: migrate to css module
* refactor: migrate to css module
* refactor: migrate to tanstack table
* docs: clarify price precision comment
* chore: remove comment
* chore: remove comment
* fix: disable isDirty in case of llm pricing
* refactor: number
* feat: add search , dropdown and flag
* feat: feature flag on entire route and add mode costs tabs
* fix: add isFetchingFeatureFlags
* feat: llm pricing unpriced model
* chore: update flag
* refactor: shell
* fix: add key to route
* feat: add flags
* chore: additional refactor
* chore: add commet in utis
* chore: self review changes
* refactor: types and other things
* refactor: types and other things
* chore: add disable on source id
* empty commit
* feat: add search to search model name
* chore: empty commit
* fix: add demo side nav on sidenav
* chore: remove demo side nav
* refactor: update routes
* chore: remove usd selector for now
* fix: layout shift
* refactor: styles
* refactor: typography component
* refactor: more changes
* refactor: typograhy
* refactor(llm-pricing): break model-cost drawer into per-component files + tokens
Apply the CSS-module/component conventions to the drawer that came from
drawer-3:
- Move the drawer under ModelCostTabPanel/components/ModelCostDrawer/ to mirror
the ModelCostsTable structure
- Split the single 395-LOC ModelCostDrawer.module.scss into per-component
co-located modules; cross-component selectors live in shared.module.scss and
are pulled in via CSS-modules `composes`
- shared.module.scss is a composes target (parsed as plain CSS), so it is kept
flat with block comments — no SCSS nesting or // comments
- Use --text-vanilla-* (not --bg-vanilla-*) for text colors, matching the
listing code
* refactor: more changes
* refactor: styling and components
* refactor: styling and components
* chore: add a tooltip on hover
* feat: add delete confirm modal
* fix: update title
* refactor: css variables
* refactor: use signoz button and minor css update
* chore: sync table
* chore: remove extra comment
* chore: use typograpgy test in table config
* fix: minior issues
* fix: llm pricing listing
* refactor: remove extra classes
* refactor: side nav changes
* fix: update missing styles
* chore: update edit and delete options
* chore: remove extra comment
* chore: revert env changes
* chore: add enable check
* chore: remove divider
* refactor: use delete confirm dialog
* chore: remove scss file
* feat: move ui to easily accessable tabs
* feat: update test cases
* chore: update text
* chore: self review changes
* chore: self review refactor
* chore: self review changes
* feat(llm-pricing): batch-map unpriced models via top Save + confirm drawer
Replace the per-row "Map to model" button + inline confirm with a single
top-level Save button that opens a confirm drawer listing every pending
mapping in a table (model -> billing model + pricing).
- Batch the save: group selections by target rule so multiple models mapped
to the same rule append their patterns in one PUT instead of clobbering
- Capped, skeleton-loaded unpriced table (max-height) to avoid layout shift,
matching the model-costs table
- Restore the dropped .modelCellName style; drop dead inline-confirm styles
- Tests: batch/clobber hook test, tab integration flow, and fix the stale
'unpriced tab disabled' assertion (the tab is now enabled)
* chore: remove worktree
* chore: revert env.ts
* chore: update llm pricing
* chore: update ui
* chore: sync unpriced modal
* chore(llm-pricing): address Copilot review on unpriced confirm table
- formatSpanCount: use Intl.NumberFormat compact so 999,999 renders as
"1M" (not "1000.0K") and trailing ".0" is dropped
- confirm drawer extra-buckets container: non-wrapping inline-flex so
buckets stay on one line at the fixed row height
- soften the misleading "renders identically" comment (extra buckets are
plain inline text here, not the model-costs Badge chips)
* chore: flex wrap
* chore: self review changes
* feat: add support to remove model
* chore: update ux
* chore: update test cases
* chore: remove unused test cases
* chore: update tests
* chore: remove extra integration
* chore: refactor
* refactor: imports
* refactor: un priced modal
* chore: update unpriced modal
* chore: update map billing mode select
* refactor: code
* refactor: remove comments
* chore: make bg great again
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
* feat(authz): store role transaction groups as document of record
Persist a role's transaction groups as JSON on the role row so the role
details page is reconstructed deterministically from SQL instead of being
rebuilt from OpenFGA tuples (which will soon carry opaque hashed telemetry
selectors):
- authtypes: TransactionGroups gains Value/Scan (validated via
NewTransactionGroups) and MarshalJSON (nil renders as []); NewRole takes
transactionGroups; NewManagedRoles fills managed docs from the registry;
RoleWithTransactionGroups removed - Role carries the wire field and the
AuthZ interface, handler, and OpenAPI responses use *Role; GettableRole
(without transactionGroups) is the list response
- sqlmigration 099: add role.transaction_groups, backfill custom roles from
their permission tuples (dual dialect) and managed roles from the registry
- sqlmigration 059: pin insert columns so the live Role model addition does
not break fresh installs (059 runs before 099)
- ee provider: writes persist the doc alongside FGA tuples (FGA first, SQL
second, as before); GetWithTransactionGroups reads the doc; the per-type
ReadTuples fan-out (readAllTuplesForRole) is removed
- audit middleware: log and skip resolved resources carrying a resolution
error
- frontend: regenerated OpenAPI spec and API types; role list consumers
retyped to GettableRole; role GET keeps transactionGroups
* fix(authz): reconcile role tuples from openfga state, decouple migration 059
- Update and Delete derive their diff/deletion base from the tuples openfga
actually holds for the role (readAllTuplesForRole) instead of the stored
JSON record, so every mutation sweeps drift and residue; the record stays
a display-only artifact written after the tuple write
- ReadTuples restored on the AuthZ interface with plain passthroughs in both
providers and the ee server
- TransactionGroups.Value marshals unconditionally (nil renders as [] via
MarshalJSON) instead of returning a nil driver.Value
- migration 059 uses a migration-local role struct and constructor so live
Role model changes cannot alter its insert; migration 099 drops the manual
column-exists guard (AddColumn emits IF NOT EXISTS)
* refactor(authz): split role into domain Role and StorableRole
Replace the Scan/Value/MarshalJSON codecs on TransactionGroups with the
storable pattern: StorableRole is the bun model carrying transaction groups
as raw JSON text, Role is the pure domain/wire type, and
NewStorableRoleFromRole/NewRoleFromStorableRole convert at the store
boundary (nil groups persist as [], reads parse through the validating
constructor). RoleStore and sqlauthzstore speak StorableRole; both
providers convert; handlers and the wire contract are unchanged.
* revert(authz): restore TransactionGroups codecs over the storable split
Role is a bun relation target (UserRole.Role, ServiceAccountRole.Role), so
splitting it into StorableRole/Role cascaded: relations must point at the
bun model, which broke the user-roles join and leaked the storable shape
into user and service account responses. Keep the single Role model with
Scan/Value/MarshalJSON on TransactionGroups; the storable split fits leaf
models only.
This reverts commit 73aa7d32b1 and keeps transaction_test.go deleted.
* refactor(authz): use bun models in migration 099, wire oss role get
- migration 099 follows the migration-local row struct pattern: bun
NewSelect/NewUpdate for the role table reads and backfill writes; the
openfga store and tuple lookups stay raw like 081/083
- oss provider Get reads the role from the store instead of returning
unsupported
* refactor(authz): org-scoped backfill in migration 099, empty groups on null scan
- migration 099 iterates organizations: per org it backfills custom roles
from their permission tuples (readRoleTuples helper) and managed roles
from the registry (JSON precomputed per role name)
- TransactionGroups scans SQL NULL as an empty slice so the api always
renders transactionGroups as []; nullzero keeps writing NULL for nil
* fix(authz): pass unique constraints to add column in migration 099
* feat(llm-pricing): add model pricing foundation (route, permission, page shell)
* feat(llm-pricing): add listing page and table
* chore(llm-pricing): drop search + source filters from list request
The list API does not honour the q (search) and source params yet, so
the controls did nothing. Remove the search input and source dropdown
along with the params we sent, and trim useModelPricingFilters to the
URL-backed page state that pagination still needs. Currency dropdown,
tabs, table and pagination are unchanged. Filters will return once the
backend supports them.
* refactor(llm-pricing): extract getRelativeTime helper in utils
Pull the relative-time formatting out of getRelativeLastSeen into a
small local getRelativeTime helper. Kept feature-local (not in the
shared utils/timeUtils) so the LLM pricing module owns its own dayjs
config; the local relativeTime extend stays for test self-sufficiency.
* refactor(llm-pricing): drop dead NaN guard in formatPricePerMillion
Pricing fields are typed as required numbers and JSON can't carry NaN,
so Number.isNaN was unreachable. Keep the null/undefined guard as API
defensiveness (toFixed on a missing value would crash the row). Also
trims the now-redundant dayjs.extend comment.
* refactor(llm-pricing): centralize constants and shared types
Extract PAGE_SIZE, PAGE_KEY, COLUMN_COUNT and CURRENCY_OPTIONS into a
new constants.ts, and move the ModelPricingFilters contract into
types.ts. Component prop interfaces stay colocated with their
components, matching the convention in the drawer PR.
* refactor(llm-pricing): use nuqs for list pagination URL state
Replace the hand-rolled useHistory + URLSearchParams plumbing in
useModelPricingFilters with nuqs useQueryState, matching the convention
used by the dashboards, alerts and k8s list pages. Behaviour is
unchanged: parseAsInteger.withDefault(1) keeps ?page=1 out of the URL
and history:'replace' avoids polluting the back-stack.
* refactor(llm-pricing): inline pagination, drop useModelPricingFilters
The hook had shrunk to a one-line nuqs wrapper after search/source were
removed, so inline the useQueryState call into the container and remove
the hook file plus the now-unused ModelPricingFilters type. When the
filters return (once the API honours them) they can move back into a
dedicated hook.
* feat(llm-pricing): disable currency selector (USD-only for now)
Only USD is priced today, so render the currency SelectSimple in a
disabled state pinned to USD. A disabled select can't fire onChange, so
the currency useState is dead — drop it (and the now-unused useState
import).
* refactor(llm-pricing): render model costs inside its tab + tab URL param
The listing was rendered outside the Tabs, so the tab was decorative.
Move all model-cost content (currency control, list query, table,
pagination, footer) into a ModelCostsTab component rendered as the
'Model costs' tab's children, and drive the active tab from a 'tab' URL
query param (nuqs). The container is now just the page shell. Unpriced
models stays a disabled placeholder for a later PR.
* style(llm-pricing): target @signozhq table slots, drop dead antd/leftover rules
The component uses @signozhq/ui Table/Tabs (Radix-based), not antd, so the
.ant-table-* and .ant-tabs-nav selectors never matched — the intended
uppercase/muted header styling wasn't applied. Retarget header/cell rules to
[data-slot='table-head'|'table-cell'] (no !important needed). Also remove dead
rules left over from the removed search/source/add UI (.filters-bar__search,
__source, __add, .page-header__actions) and the unused .source-badge--auto/
--override modifiers.
* fix(llm-pricing): constrain currency dropdown width, drop tab URL param
- Currency SelectSimple stretched to fill the filters bar; give it a fixed
160px width (min-width couldn't cap the trigger).
- Model costs is the only enabled tab for now, so use Tabs defaultValue
instead of a URL-backed param. Removes the nuqs tab state plus the now-unused
TAB_KEYS/TAB_QUERY_KEY constants and TabKey type.
* chore: self review changes
* fix: add skeleton loading
* refactor: self review changes
* refactor: initial prop
* fix: update styling
* fix: add comments in utils
* feat(llm-pricing): add model cost drawer and wire into listing page
* fix(llm-pricing): restrict pricing management to admins
Align the frontend write gate with the backend, which protects the
LLM pricing create/update/delete endpoints with AdminAccess (admin
only). Previously manage_llm_pricing allowed EDITOR/AUTHOR, so those
roles saw the Add/Save affordances but their writes were rejected with
a 403. Also removes the AUTHOR entry, which could never reach the page
(the route gate excludes it).
* fix(llm-pricing): read-only drawer shows View title, hides source picker
Non-managers open the drawer in view mode (write APIs are Admin-only), so:
- the heading reads "View model cost" instead of "Edit model cost"
- the Source (auto vs. override) picker is hidden, since switching source is
a manager-only action with nothing actionable for a viewer.
* refactor: form in edit / add modal
* chore: update color tokens
* fix: add error handling
* chore: update more self review changes
* chore: self review changes
* chore: self review changes
* fix: minor grammer thing
* fix: route thing
* refactor: migrate to css moduel
* refactor: migrate to css module
* refactor: migrate to css module
* refactor: migrate to tanstack table
* docs: clarify price precision comment
* chore: remove comment
* feat(llm-attribute-mapping): add attribute mapping foundation (route, permission, page shell)
* fix: css styling
* refactor: css module
* feat(llm-attribute-mapping): read-only listing on CSS modules [2/5]
Rebase the listing slice onto the foundation's CSS-module refactor
(which deleted the global stylesheet) and migrate it accordingly:
- Merge listing styles into LLMObservabilityAttributeMapping.module.scss
(groups/mappers tables, source chips, index badge, error/footer).
- Convert all listing components from global BEM classNames to
styles.* module access; drop dead/style-less classes (am-table,
am-row-actions, am-add-row, *_edited, mappers-table__error).
- Adopt theme-aware semantic tokens (--l2/l3-*, --accent-primary,
--callout-error-*) in place of --bg-* primitives.
* chore: migrate tanstack table
* chore: remove comment
* fix: disable isDirty in case of llm pricing
* refactor: number
* feat: add search , dropdown and flag
* feat: feature flag on entire route and add mode costs tabs
* fix: add isFetchingFeatureFlags
* chore: update flag
* refactor: shell
* fix: add key to route
* feat: add flags
* chore: additional refactor
* chore: add commet in utis
* chore: self review changes
* refactor: types and other things
* refactor: types and other things
* chore: add disable on source id
* empty commit
* chore: empty commit
* fix: add demo side nav on sidenav
* chore: remove demo side nav
* refactor: update routes
* chore: remove usd selector for now
* fix: layout shift
* refactor: styles
* refactor: typography component
* refactor: more changes
* refactor: typograhy
* refactor(llm-pricing): break model-cost drawer into per-component files + tokens
Apply the CSS-module/component conventions to the drawer that came from
drawer-3:
- Move the drawer under ModelCostTabPanel/components/ModelCostDrawer/ to mirror
the ModelCostsTable structure
- Split the single 395-LOC ModelCostDrawer.module.scss into per-component
co-located modules; cross-component selectors live in shared.module.scss and
are pulled in via CSS-modules `composes`
- shared.module.scss is a composes target (parsed as plain CSS), so it is kept
flat with block comments — no SCSS nesting or // comments
- Use --text-vanilla-* (not --bg-vanilla-*) for text colors, matching the
listing code
* refactor: more changes
* refactor: styling and components
* refactor: styling and components
* chore: add a tooltip on hover
* feat: add delete confirm modal
* fix: update title
* refactor: css variables
* refactor: use signoz button and minor css update
* chore: sync table
* chore: remove extra comment
* chore: use typograpgy test in table config
* fix: minior issues
* fix: llm pricing listing
* refactor: remove extra classes
* refactor: side nav changes
* fix: update missing styles
* chore: update edit and delete options
* chore: remove extra comment
* chore: revert env changes
* chore: add enable check
* chore: remove divider
* refactor: use delete confirm dialog
* chore: remove scss file
* feat: move ui to easily accessable tabs
* feat: update test cases
* chore: update text
* chore: self review changes
* chore: self review refactor
* chore: self review changes
* chore: remove worktree
* chore: revert env.ts
* chore: add attribute mapping foundation
* chore: update ui and add animation
* refactor: components update
* chore: typography changes
* chore: typography changes
* chore: use badge
* refactor: basic components
* chore: remove hardcoded value
* chore: add comments & tests
* chore: update env.ts
* chore: update tests
* chore: self review changes
* chore: update test cases
* chore: remove extra comments
* refactor(llm-pricing): share toast copy via constants
* chore: use constants
* chore: redclared constants
* chore: update test cases
* chore: remove unused component
* chore: update types
* chore: update ui
* refactor: minor things
* chore: break down thingsinto comps
* chore: update files
* fix: update mapping
* chore: remove draft logic no need for now
* chore: more refactor
* chore: remove comments
* chore: refactor route
* chore: update query refech on mount
* chore: update skeleton
* refactor: code
* refactor: code
* refactor: eslint disable
* chore: update selector
* chore: sort
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
* fix(dashboard-v2): carry unsaved panel edits into view mode
Switching from the panel editor to View Mode dropped un-saved config edits
(thresholds, units, columns, legend, formatting, axes) — the View modal
re-seeded from the saved panel spec, carrying only the live query.
Hand the live draft spec off via a tab-scoped sessionStorage handoff,
correlated to the panel by dashboardId + panelId. The query stays in the URL
(compositeQuery) so the query builder hydrates; the rest of the spec rides in
sessionStorage so the edits survive a refresh without bloating the URL. The
handoff is cleared on plain View, grid drilldown, and close so it can't seed
a stale view.
* fix(dashboard-v2): hide 'Switch to Edit Mode' without edit permission
The View panel modal is reachable by read-only users, so gate the
Switch to Edit Mode button on canEditDashboard && !isLocked, mirroring
how the panel actions menu gates the Edit panel item.
* feat(dashboard-v2): track panel view/edit mode switch events
Add DashboardEvents enum and log SWITCH_TO_EDIT_MODE (View modal) and
SWITCH_TO_VIEW_MODE (panel editor) when the user toggles between the
two panel modes.
* fix(dashboard-v2): bound query cacheTime to 0 under auto-refresh
Under auto-refresh each cycle mints a fresh time-keyed query, so unused
entries accumulate and can OOM the tab. Drop cacheTime to 0 when
auto-refresh is enabled (V1 parity) for panel queries and the query/
dynamic variable selectors; keep DASHBOARD_CACHE_TIME otherwise.
Repairs 10 broken signoz.io/docs links (5 hard 404s + 5 dead anchors)
that survived the frontend-only sweep in #11319 because they live in
the Go backend and two frontend files it did not cover.
- infra-monitoring readiness checks: drop the removed `user-guides/`
path segment and remap to the current hostmetrics/k8s-metrics anchors
- querybuilder / telemetrylogs search-troubleshooting errors: point to
the reworded Q&A anchors (update matching test assertion)
- alert generatorURL fallback: `alerts-management/#generator-url` ->
`alerts/` (anchor removed in docs restructure)
- missing-spans banner: -> traces-management troubleshooting FAQ anchor
- agent-skills install link: `#installation` -> `#install-the-plugin`
Every changed URL verified live (200 + anchor present).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: rename existing export logic to follow the new export data structure
* feat(data-export): add time_series serializer
Pure serializer that walks the V5 time_series tree (results → aggregations → series) into a format-agnostic SerializedTable — tidy layout, one row per (series, timestamp), labels as columns, raw values, y-axis unit in the value header. Series names match the chart legend (getLabelName + getLegend resolve legend templates and aggregation aliases/expressions from the builder query).
* feat(data-export): add csv/jsonl formatters and timestamped client download
toCsv/toJsonl turn a SerializedTable into CSV or newline-delimited JSON; downloadFile triggers a client-side blob download with a timestamped filename (base-YYYY-MM-DD_HH-mm-ss.ext) so repeated exports never collide and record when they were taken.
* feat(data-export): add useClientExport dispatch hook
Frontend-driven export hook: narrows a V5 queryRange response by request type, serializes time_series (scalar lands with the next sub-issue), formats as csv/jsonl and downloads. Takes the builder query for chart-parity series naming. Backend-driven export stays in useServerExport.
* test(data-export): assert timestamped filenames via the naming helper
Review feedback: the filename-format regexes duplicated the format spec across tests. The hook tests now freeze the clock and assert delegation to getTimestampedFileName; the format itself stays pinned by the single exact-string test beside the function.
* feat: comment update
* feat: use exsiting request type
* feat: add functional unique index
* chore: add tag migration
* chore: add failing scratch test (to discuss)
* chore: fetch expressions as well in getindices call
* fix: remove tag unique index (will be added in a separate PR)
* fix: remove tag unique index (will be added in a separate PR)
* chore: remove temporary tests
* fix: go lint fix
* chore: better comment for unique index
* test: add test for case insensitive expression equality
* test: add equality test for columns with quotes and capital letters
* chore: add separate type for unique indices with expressions
* test: add test for postgres provider
* fix: don't return error in v2 list dashboard api if there is a v1 dashboard
* chore: generate api specs
* feat(dashboard-v2): surface legacy dashboards in the v2 list (#12024)
* feat(dashboard-v2): add legacy dashboard dialog
Surfaces a copyable dashboard ID and a Contact Support action for a
pre-v2 (legacy) dashboard that has no v2 spec to render.
* feat(dashboard-v2): hide v2-only actions for legacy dashboards
A legacy dashboard has no v2 spec, so view/open/copy-link/rename/
duplicate/lock don't apply. Gate them behind !isLegacy, leaving only
Delete (and dropping its now-orphan leading divider).
* feat(dashboard-v2): surface legacy dashboards in the v2 list
Flag legacy rows with a badge, block navigation, and open the legacy
dialog on click instead. Disable the pin action with an explanatory
tooltip and gate the row's actions menu to legacy-safe items.
---------
Co-authored-by: Ashwin Bhatkal <ashwin96@gmail.com>
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
* feat(query-v2): add metricNamespace prop
* feat(tanstack): add way to modify header padding
* feat(infra-monitoring): add new components for v2 refactor
* refactor(infra-monitoring): add docs prop for entity group header
* refactor(infra-monitoring): remove old props of empty state
* refactor(infra-monitoring): use column header to show on filter side panel
* refactor(infra-monitoring): update header to use new v2 components
* refactor(infra-monitoring): update utils and types
* refactor(infra-monitoring): update k8s base list to accept v2 apis
* refactor(infra-monitoring): update k8s base details to v2 apis
* refactor(infra-monitoring): couple fixes and refactors on components
* refactor(infra-monitoring): migrate hosts to v2
* refactor(infra-monitoring): migrate clusters to v2
* refactor(infra-monitoring): migrate daemon sets to v2
* refactor(infra-monitoring): migrate deployments to v2
* refactor(infra-monitoring): migrate jobs to v2
* refactor(infra-monitoring): migrate namespaces to v2
* refactor(infra-monitoring): migrate nodes to v2
* refactor(infra-monitoring): migrate pods to v2
* refactor(infra-monitoring): migrate statefulsets to v2
* refactor(infra-monitoring): migrate volumes to v2
* refactor(infra-monitoring): cleanup on entity details
* refactor(infra-monitoring): refactor infra monitoring to use v2
* refactor(infra-monitoring): date time border with better contrast
* chore(infra-monitoring): remove todo, placeholders are valid
* fix(pr): address comments
---------
Co-authored-by: Nikhil Mantri <nikhil.mantri1999@gmail.com>
* feat(trace-details): add Open in Logs Explorer to span details Logs tab
Restores the Open in Logs Explorer action (removed with V2 in #11805): a
toolbar button above the log list and an inline action in the no-trace-logs
empty state. Also tightens the empty-state RESOURCES card layout (stacked,
content-width, no extra height).
* feat: add logs explorer btn in footer
* test(trace-details): cover Open in Logs Explorer footer button in span details
* test(trace-details): assert log-row click opens Logs Explorer with trace/span query
* style(trace-details): fix oxfmt formatting in SpanLogs test
* fix(trace-details): pin Open in Logs Explorer footer to the panel bottom
The footer lived inside the scrolling tab content, so it was pushed below
the fold whenever the span summary above the tabs was in view. Render it as
a sibling of the scrolling panelBody instead (Logs tab only), making it a
true fixed footer that is always visible.
* fix(ruletypes): tag rule threshold targets as format: double
BasicRuleThreshold.{TargetValue,RecoveryTarget} and RuleCondition.Target are
*float64 but emitted a bare 'type: number' (swaggest sets format: double only for
non-pointer floats). Bare number makes oapi-codegen clients generate float32, so a
value like 0.8 loses precision on round-trip. Tag them format:double to match
non-pointer float64 fields (e.g. MetrictypesComparisonSpaceAggregationParam).
* chore(frontend): regenerate API client for rule threshold format: double
Reflects the format: double schema change on the rule threshold/condition
targets in the orval-generated client (oxfmt + oxlint applied).
* feat(public-dashboard): detect v1 vs v2 schema for the public viewer
Anonymous public viewers have no feature flags, so the schema can't be read from
use_dashboard_v2. Probe the v2 model endpoint first and fall back to v1 only on the
'dashboard_invalid_data' (HTTP 501) schema-mismatch signal. Probing v2 first also stops
the v1 endpoint from serving v2 dashboards with un-redacted queries.
* feat(public-dashboard): fetch v2 public panel data by key
Adds a by-key fetcher over the anonymous /api/v2/public/dashboards/{id}/panels/{key}/query_range
endpoint (the generated client omits the startTime/endTime params) and a store-free
usePublicPanelQuery that mirrors usePanelQuery's PanelQueryData shape. No variables and no
pagination — the public endpoint supports neither.
* feat(public-dashboard): render v2 public dashboards read-only
Adds a read-only v2 viewer that reuses the authenticated V2 panel renderers
(PanelHeader with hideActions, PanelBody, panel registry) and the pure layoutsToSections
util, with a forked read-only grid. The public page branches on the resolved schema:
v1 keeps the existing container, v2 renders the new viewer. Dashboard variables are not
rendered — the public endpoint does not substitute them.
* feat(public-dashboard): match the standard auto-refresh control
Replace the hand-rolled 'Off' select (which was styled inconsistently and clipped its
options) with a PublicAutoRefresh that mirrors the app's DateTimeSelectionV2 refresh cluster:
a grouped refresh button + auto-refresh popover (Auto Refresh checkbox + full interval list),
portal-rendered so nothing clips. It's prop-driven — the public viewer keeps managing its own
time window — so the container now tracks enabled + interval and exposes a manual refresh.
Also nudge the header-right gap 8→12px.
* feat(public-dashboard): declare v2 query_range params, drop the wrapper, address review
Declare startTime/endTime as query params on the v2 public query_range endpoint via
RequestQuery and regenerate the OpenAPI spec + orval client, so the generated
getPublicDashboardPanelQueryRangeV2 carries them. usePublicPanelQuery now calls the
generated fetcher directly and the hand-written wrapper is removed.
Also from review: drop the defensive panelDefinition guard so an unsupported kind
surfaces loudly, use lodash noop, and trim excessive comments across the v2 files.
* fix: bind query params from PublicWidgetQueryRangeParams
---------
Co-authored-by: Naman Verma <naman.verma@signoz.io>
* fix(dashboard-v2): scroll the saved, cloned, or added panel/section into view
Saving a panel, closing the editor, cloning a panel, or adding a section
now reveals the affected panel/section instead of leaving the dashboard
scrolled to the top. Save resolves with the persisted panel id, a small
scroll-target store hands it to the grid, and a shared
scrollIntoViewWhenReady util polls for the optimistic DOM commit.
* fix(dashboard-v2): seed the metric unit per the kind's formatting controls
Replaces useMetricYAxisUnit with useSeedMetricUnit, driven by the kind's
Formatting controls (the same source buildPluginSpec reads): kinds with a
panel-wide unit seed formatting.unit; Table, which has none, seeds each
resolved value column's formatting.columnUnits instead. Column unit
selectors now also surface the metric-unit mismatch warning. Spec
read/write goes through a shared formattingSpec util.
* fix(dashboard-v2): stop grid placement from overlapping tall panels
findFreeSlot now probes the last-row candidate against every existing
item (mirroring the backend's overlap rejection) before placing there — a
tall panel from an earlier row can reach down into the last row — and
falls back to a fresh bottom row otherwise. Move-to-section reuses the
same primitive instead of always dropping to the bottom.
* fix(dashboard-v2): carry supported config across panel type switches
Switching visualization kind used to keep only formatting and thresholds,
dropping axes entirely and resetting legend/visualization/chartAppearance
to defaults. Each section seed now carries the old spec's fields the
target kind's controls declare: axes softMin/softMax/log scale, the time
preference, stacking/fillSpans, legend position, and chart appearance.
Unsupported fields (and legend customColors, keyed by series labels the
new kind may not reproduce) are still dropped.
* fix(dashboard-v2): keep context link editor alive on undecodable URL params
A literal % in a query value (e.g. ?search=95%) is not valid
percent-encoding, so decodeURIComponent threw URIError while seeding the
edit dialog and unmounted the page. Fall back to the raw string when a
key or value fails to decode.
* fix(dashboard-v2): don't auto-run the preview when a query is added
The query-type/datasource effect re-commits the draft on any structural
builder change, so adding a query immediately refetched the preview for
a still-empty query. Track the previous datasource list and skip the
commit when the change is a pure append — the new query commits on Run
Query as usual.
* fix(dashboard-v2): bootstrap variables and edit context on the editor route
A hard refresh (or direct URL) of the full-page editor mounts without
DashboardContainer or the variables bar, so the preview fetched with
unresolved variables and the store's edit context stayed cold. The
selection seeding moves out of useVariableSelection into a standalone
useSeedVariableSelection (URL → persisted store → default, with stale
URL entries pruned), which PanelEditorPage now mounts alongside
useResolvedVariables and the edit-context seed. The ?variables= param
is carried across dashboard ↔ editor navigation (withVariablesSearch)
so the selection survives the round trip (V1 parity).
* fix(dashboard-v2): carry thresholds and units onto table columns
Switching timeseries/number → table carried thresholds with an empty
columnName, which the save API rejects (TablePanelSpec.Thresholds[]
.ColumnName is required), and dropped the panel-wide unit entirely
(Table has no formatting.unit).
The renderer's keying rule is extracted as getAggregationColumnKey
(query name, or name.expression on multi-aggregation queries), and
getTableColumnKeys derives every enabled query's value-column keys
from the spec's queries alone, before any data exists — skipping
clickhouse_sql, whose columns key by the response's SQL alias.
Carried thresholds key onto the first derivable column and are
dropped when none exists instead of blocking the save; the panel-wide
unit fans out to every value column via formatting.columnUnits. The
carry is one-way: per-column units never seed a panel-wide unit back.
* chore: pr review changes
* chore: qb sticky header in view mode fix
* chore: pr review changes
* chore: pr review changes
- Unify panel/section reveal-scroll into one useScrollIntoView(id, ref, block)
hook backed by a single scrollTargetId store (was two hooks + two ids).
- buildPluginSpec seeds return their slice (empty {}/[] when nothing to carry)
instead of undefined; the omit-empty decision is centralized.
* chore: added types for containers
* chore: added querier query and constants
* chore: added helper queries
* chore: added wiring
* chore: added open api spec
* chore: endpoint and variable rename
* chore: use recency for container status.reason as well
* chore: pass orgID to getMetadata for containers (adapt to rebased main)
* chore: add metrics to the containers list for metadata and earliest time
* chore: corrected metrics list for metadata lookup
* chore: added changes to the checks API for new kube containers section
* chore: containers query modified
* chore: added integration tests
* chore: integration tests
* chore: goroutines in container monitoring
* chore: constants deduplication
* chore: inlined requests.Post call for this PR instead of wrapping in a function
* fix(dashboard-v2): close the 'updated elsewhere' dialog on reload
Reload set dismissedAt to null and relied on the refetched loaded copy's updatedAt
converging with the freshness query's serverUpdatedAt to hide the prompt. Those are two
separate queries and the convergence is racy, so the dialog often stayed open across
repeated reloads until Dismiss was clicked. Make Reload acknowledge the current version
(like Dismiss) and then refetch, so it closes on click; a genuinely newer server version
still re-prompts.
* fix(dashboard-v2): only prompt when the server copy is strictly newer
The stale-check compared the loaded and freshness copies of updatedAt with !==. Your own
edits advance the loaded copy immediately (the optimistic patch writes the PATCH response's
new updatedAt into the render cache), while the freshness query only re-fetches on window
focus. So right after creating a panel, renaming, or editing a variable, loaded jumped ahead
while freshness lagged, and !== flagged your own change as 'updated elsewhere' — a false
prompt that persisted across in-app navigation until a real window-focus refetch.
Compare with a strictly-newer (>) check so the prompt only fires when the server has a
version we have not loaded. A genuine external change still trips it; our own edits (loaded
ahead of the lagging freshness copy) no longer do.
* fix(dashboard-v2): probe freshness on visibilitychange, not refetchOnWindowFocus
The freshness check used a separate react-query query with initialData + refetchOnMount:false
so the first load made no extra request. But a query that only holds seeded data and never
fetched isn't reliably eligible for react-query's focus refetch, so on tab return no request
fired and external changes went undetected.
Drop the separate query and probe the server's updatedAt explicitly on 'visibilitychange'
(guarded to the visible transition): zero requests on first load, exactly one per tab return,
no dependence on react-query focus heuristics or staleTime bookkeeping. Comparison and
reload/dismiss behaviour are unchanged (strictly-newer; reload = dismiss + refetch).
* fix(dashboard-v2): probe on window focus too, and gate stale prompt on spec content
Two fixes to the freshness probe:
- Also listen to window 'focus', not just document 'visibilitychange'. Switching to
another app (e.g. the editor) and back keeps the browser tab 'visible', so
visibilitychange never fires and no probe ran — the reported 'no call when coming
back to the page'. focus covers that; visibilitychange still covers tab switches.
- Prompt only when the server's spec actually differs (deep-equal), gated by
strictly-newer. updatedAt is bumped by metadata-only writes like lock/unlock that
don't change what the viewer sees; keying on spec makes those a non-event without
needing the (void) lock/unlock call to return the new updatedAt. Hook now takes the
loaded dashboard so it has both updatedAt and spec to compare.
* docs(dashboard-v2): clarify stale-check probes on both tab switch and app focus
* docs(dashboard-v2): trim useDashboardStaleCheck comments
* fix(dashboard-v2): detect external tag and lock changes too
Widen the stale-check content comparison from spec-only to spec + tags + lock state, so an
external tag edit or lock/unlock by another user surfaces the reload prompt. Own actions still
don't trip it: the render cache advances with them (optimistic patch / cache sync), so loaded
matches server.
* fix(dashboard-v2): source Home recent dashboards from the v2 list under the flag
* fix(dashboard-v2): keep same-key:value tags from collapsing to one word in the list
* fix(dashboard-v2): commit the inline dashboard-title edit on outside click
* fix(dashboard-v2): cap variable multi-select pills so the bar doesn't overflow
* fix(dashboard-v2): persist the variables-bar expand state and show "Less" when it loads expanded
* feat(dashboard-v2): reusable dashboard image picker for details + create modal
Extract the icon/image picker into a shared DashboardImagePicker used by both the dashboard settings form and the create-dashboard modal. Patch `/image` with add (not replace) when the field is absent, and lift the inline dropdown above the create-modal form fields.
* feat(dashboard-v2): simplify the create-modal templates tab to browse-and-request
Drop the placeholder gallery (search + category list + preview) for a clean, centered browse-and-request block: link out to the published template library, and let cloud users request a template we haven't built yet.
* fix(dashboard-v2): contain Escape/Enter and pass Cmd+Enter through the tag editor
Escape/Enter no longer bubble to close the host drawer/modal; Cmd/Ctrl+Enter passes through so a host form can submit on it (plain Enter still adds/commits a tag).
* feat(dashboard-v2): add/edit a dashboard's tags from the list
Add an "Add Tags"/"Edit Tags" kebab action (labelled by whether the dashboard has tags) opening the shared key:value editor, saving via an `add /tags` patch, with Cmd/Ctrl+Enter save. Gated on edit permission, disabled while locked. Also guard row navigation so clicks inside portaled overlays (menu/modals) don't open the dashboard.
* fix(dashboard-v2): tint chart-legend checkbox with its series color
The @signozhq/ui Checkbox declares --checkbox-checked-background on the
element itself via its data-color rule, so a value set on an ancestor was
shadowed and the per-series tint never applied. Feed the color through a
private --series-color var and override the vars on the element with a
selector that out-specifies the library rule.
* fix(dashboard-v2): make antd popups usable inside the view panel modal
The editor's antd Selects/pickers portal to document.body, outside the
modal Radix dialog, where Radix disables pointer events and traps focus, so
their menus opened but couldn't be clicked. Wrap the modal body in an antd
ConfigProvider whose getPopupContainer points at the dialog content, so all
antd popups render inside the interactive, focus-trapped layer.
* feat(dashboard-v2): add the list columns editor and pagination loader to the view modal
Surface the List panel's columns editor in the View modal (as in the full
editor) by exposing setSpec from useViewPanelMode and wiring it into the
query-builder footer. Its add-column combobox portals to body, so inside
the modal it opened behind the dialog — lift its z-index above the
query-builder popups so it stacks on top.
Also thread isPreviousData into PreviewPane so the List panel's page-change
loader shows during pagination, matching the full editor and dashboard.
* fix(dashboard-v2): measure chart dimensions before first paint
useResizeObserver seeded its size to 0 and only reported real dimensions
after paint via its debounced observer, so charts rendered once at 0 then
jumped — most visibly the pie legend, which rendered left-aligned then
re-centered once it learned it fit on one row. Add a useLayoutEffect that
measures the element synchronously before paint. In jsdom clientWidth is 0,
so tests are unaffected.
* feat(dashboard-v2): support per-slice color overrides for pie charts
Enable the legend colors control for pie charts. The control is fed by
useLegendSeries, which only handled time-series data, so pie returned no
series. Resolve pie legend series from the same scalar slices the renderer
draws (without overrides, for default colors) so the color keys match what
the chart looks up.
Extract the series resolution into utils/legendSeries.ts (a shared dedupe
primitive plus a pie and a time-series resolver) and reduce the hook to a
kind switch that returns none for kinds without a colors control.
* feat(dashboard-v2): offer dashboard root and every section in the move menu
Rework the "Move to section" submenu to list the dashboard root plus every
titled section (minus the panel's own), instead of only titled sections with
a separate "move out of section" entry. Hide it entirely when there is
nowhere to move.
* fix(dashboard-v2): hide the empty panel header in editor preview
When a panel has no title, description, or status and the actions menu is
suppressed (editor preview), render nothing instead of an empty header bar.
Add a min-height so the header keeps a stable size when it does render.
* fix(dashboard-v2): lazy-load panels per-panel instead of per-section
Move the viewport intersection observer down from Section to a new
SectionGridItem wrapper so each panel gates its own fetch. A board with
many panels now only queries the ones actually on screen, instead of
loading every panel in any section that scrolls into view.
* fix(dashboard-v2): hide "Extend time range" for panels with a fixed time preference
A panel locked to a non-global timePreference queries a fixed relative span
anchored to the dashboard end, so widening the ambient window can't surface
data for it — the "Extend time range" CTA was a no-op. NoData now takes the
panel and, via panelHasFixedTimePreference, drops the global extender for such
panels (only Retry remains). The View modal's local extender still wins.
* refactor(dashboard-v2): read panel time preference via shared helper
Panel.tsx read visualization.timePreference through an inline cast; replace it
with getPanelTimePreference so the header pill and NoData's extend gate share
one reader.
* feat(dashboard-v2): support per-series legend colors for bar and histogram panels
Route BarChartPanel and HistogramPanel through the time-series legend path in
useLegendSeries and enable the Legend `colors` control in both kinds' sections,
extending the per-series color overrides to these kinds.
* feat(dashboard-v2): pin the query-type tabs to the top of the editor scroll area
Make the query-type tab nav + Run Query button sticky so they stay visible
while the query body scrolls underneath; move the top padding onto the nav so
it keeps its spacing at rest and when pinned.
* feat(trace-details): full-width span percentile panel with visible borders
* refactor(trace-details): give TraceIdField its own CSS module
* fix(trace-details): single borders for docked span details
* feat(trace-details): rework span details panel
Extract the summary into a SpanSummary component, place it by panel width
(above the tabs when narrow, inside Overview when wide), single-scroll sticky
tabs, and fix service/status-message badge overflow + truncation.
* feat(trace-details): share metadata row across header and span summary
* test: update test cases
* refactor(infra-monitoring): remove tabs for containers/processes from hosts
* fix(traces): parse http code to int instead of just render sakura color
* test(entity-details): add tests for entity traces
* fix(pr): address comments
* feat(llm-pricing): add model pricing foundation (route, permission, page shell)
* feat(llm-pricing): add listing page and table
* chore(llm-pricing): drop search + source filters from list request
The list API does not honour the q (search) and source params yet, so
the controls did nothing. Remove the search input and source dropdown
along with the params we sent, and trim useModelPricingFilters to the
URL-backed page state that pagination still needs. Currency dropdown,
tabs, table and pagination are unchanged. Filters will return once the
backend supports them.
* refactor(llm-pricing): extract getRelativeTime helper in utils
Pull the relative-time formatting out of getRelativeLastSeen into a
small local getRelativeTime helper. Kept feature-local (not in the
shared utils/timeUtils) so the LLM pricing module owns its own dayjs
config; the local relativeTime extend stays for test self-sufficiency.
* refactor(llm-pricing): drop dead NaN guard in formatPricePerMillion
Pricing fields are typed as required numbers and JSON can't carry NaN,
so Number.isNaN was unreachable. Keep the null/undefined guard as API
defensiveness (toFixed on a missing value would crash the row). Also
trims the now-redundant dayjs.extend comment.
* refactor(llm-pricing): centralize constants and shared types
Extract PAGE_SIZE, PAGE_KEY, COLUMN_COUNT and CURRENCY_OPTIONS into a
new constants.ts, and move the ModelPricingFilters contract into
types.ts. Component prop interfaces stay colocated with their
components, matching the convention in the drawer PR.
* refactor(llm-pricing): use nuqs for list pagination URL state
Replace the hand-rolled useHistory + URLSearchParams plumbing in
useModelPricingFilters with nuqs useQueryState, matching the convention
used by the dashboards, alerts and k8s list pages. Behaviour is
unchanged: parseAsInteger.withDefault(1) keeps ?page=1 out of the URL
and history:'replace' avoids polluting the back-stack.
* refactor(llm-pricing): inline pagination, drop useModelPricingFilters
The hook had shrunk to a one-line nuqs wrapper after search/source were
removed, so inline the useQueryState call into the container and remove
the hook file plus the now-unused ModelPricingFilters type. When the
filters return (once the API honours them) they can move back into a
dedicated hook.
* feat(llm-pricing): disable currency selector (USD-only for now)
Only USD is priced today, so render the currency SelectSimple in a
disabled state pinned to USD. A disabled select can't fire onChange, so
the currency useState is dead — drop it (and the now-unused useState
import).
* refactor(llm-pricing): render model costs inside its tab + tab URL param
The listing was rendered outside the Tabs, so the tab was decorative.
Move all model-cost content (currency control, list query, table,
pagination, footer) into a ModelCostsTab component rendered as the
'Model costs' tab's children, and drive the active tab from a 'tab' URL
query param (nuqs). The container is now just the page shell. Unpriced
models stays a disabled placeholder for a later PR.
* style(llm-pricing): target @signozhq table slots, drop dead antd/leftover rules
The component uses @signozhq/ui Table/Tabs (Radix-based), not antd, so the
.ant-table-* and .ant-tabs-nav selectors never matched — the intended
uppercase/muted header styling wasn't applied. Retarget header/cell rules to
[data-slot='table-head'|'table-cell'] (no !important needed). Also remove dead
rules left over from the removed search/source/add UI (.filters-bar__search,
__source, __add, .page-header__actions) and the unused .source-badge--auto/
--override modifiers.
* fix(llm-pricing): constrain currency dropdown width, drop tab URL param
- Currency SelectSimple stretched to fill the filters bar; give it a fixed
160px width (min-width couldn't cap the trigger).
- Model costs is the only enabled tab for now, so use Tabs defaultValue
instead of a URL-backed param. Removes the nuqs tab state plus the now-unused
TAB_KEYS/TAB_QUERY_KEY constants and TabKey type.
* chore: self review changes
* fix: add skeleton loading
* refactor: self review changes
* refactor: initial prop
* fix: update styling
* fix: add comments in utils
* feat(llm-pricing): add model cost drawer and wire into listing page
* fix(llm-pricing): restrict pricing management to admins
Align the frontend write gate with the backend, which protects the
LLM pricing create/update/delete endpoints with AdminAccess (admin
only). Previously manage_llm_pricing allowed EDITOR/AUTHOR, so those
roles saw the Add/Save affordances but their writes were rejected with
a 403. Also removes the AUTHOR entry, which could never reach the page
(the route gate excludes it).
* fix(llm-pricing): read-only drawer shows View title, hides source picker
Non-managers open the drawer in view mode (write APIs are Admin-only), so:
- the heading reads "View model cost" instead of "Edit model cost"
- the Source (auto vs. override) picker is hidden, since switching source is
a manager-only action with nothing actionable for a viewer.
* refactor: form in edit / add modal
* chore: update color tokens
* fix: add error handling
* chore: update more self review changes
* chore: self review changes
* chore: self review changes
* fix: minor grammer thing
* fix: route thing
* refactor: migrate to css moduel
* refactor: migrate to css module
* refactor: migrate to css module
* refactor: migrate to tanstack table
* docs: clarify price precision comment
* chore: remove comment
* feat(llm-attribute-mapping): add attribute mapping foundation (route, permission, page shell)
* fix: css styling
* refactor: css module
* chore: remove comment
* fix: disable isDirty in case of llm pricing
* refactor: number
* feat: add search , dropdown and flag
* feat: feature flag on entire route and add mode costs tabs
* fix: add isFetchingFeatureFlags
* chore: update flag
* refactor: shell
* fix: add key to route
* feat: add flags
* chore: additional refactor
* chore: add commet in utis
* chore: self review changes
* refactor: types and other things
* refactor: types and other things
* chore: add disable on source id
* empty commit
* chore: empty commit
* fix: add demo side nav on sidenav
* chore: remove demo side nav
* refactor: update routes
* chore: remove usd selector for now
* fix: layout shift
* refactor: styles
* refactor: typography component
* refactor: more changes
* refactor: typograhy
* refactor(llm-pricing): break model-cost drawer into per-component files + tokens
Apply the CSS-module/component conventions to the drawer that came from
drawer-3:
- Move the drawer under ModelCostTabPanel/components/ModelCostDrawer/ to mirror
the ModelCostsTable structure
- Split the single 395-LOC ModelCostDrawer.module.scss into per-component
co-located modules; cross-component selectors live in shared.module.scss and
are pulled in via CSS-modules `composes`
- shared.module.scss is a composes target (parsed as plain CSS), so it is kept
flat with block comments — no SCSS nesting or // comments
- Use --text-vanilla-* (not --bg-vanilla-*) for text colors, matching the
listing code
* refactor: more changes
* refactor: styling and components
* refactor: styling and components
* chore: add a tooltip on hover
* feat: add delete confirm modal
* fix: update title
* refactor: css variables
* refactor: use signoz button and minor css update
* chore: sync table
* chore: remove extra comment
* chore: use typograpgy test in table config
* fix: minior issues
* fix: llm pricing listing
* refactor: remove extra classes
* refactor: side nav changes
* fix: update missing styles
* chore: update edit and delete options
* chore: remove extra comment
* chore: revert env changes
* chore: add enable check
* chore: remove divider
* refactor: use delete confirm dialog
* chore: remove scss file
* feat: move ui to easily accessable tabs
* feat: update test cases
* chore: update text
* chore: self review changes
* chore: self review refactor
* chore: self review changes
* chore: remove worktree
* chore: revert env.ts
* chore: add attribute mapping foundation
* refactor: basic components
* chore: update env.ts
* chore: update tests
* chore: self review changes
* chore: update test cases
* chore: remove extra comments
* refactor(llm-pricing): share toast copy via constants
* chore: use constants
* chore: redclared constants
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
* refactor(query-builder-v2): ensure query builder has consistent colors and allow customization
* fix(query-builder-v2-usages): ensure each place using query builder v2 has consistent colors
* fix(alert): revert l3 for v2 ui
* fix(trace): remove double border at flamegraph/waterfall juncture
* fix(trace): square accordion header corners
* fix(trace-waterfall): scope span hover-card to the span-name column
* feat(trace): move missing-spans banner to header using Callout
* feat(trace): use Lucide chevrons for accordion expand icons
* fix(trace-waterfall): keep hovered rows at full opacity when dimmed
* fix(trace-waterfall): strengthen selected/hover highlight into one continuous band
* fix(trace-waterfall): soften row-action button gradient
* fix(trace-waterfall): extend span-name divider to the top of the container
* fix(trace): use neutral info icon for flamegraph sampling notice
* fix(trace): tighten timeline ruler ticks and labels
* feat(trace-waterfall): enable timeline crosshair aligned to padded ruler
* fix(trace): match crosshair badge unit to ruler ticks
* test(querier): move querier tests into per-signal suites
Pure git renames into querier{logs,traces,metrics,scalar,common} + CI matrix;
monoliths are split in the next commit. Preserves history (git log --follow).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rt2Xi6TdBDBAc3mhYZAb9q
* test(querier): split querier monoliths into theme files
Split 01_logs/04_traces/03_metrics/06_order_by_table into theme files
(functions moved verbatim, 249 tests unchanged); scalar helper -> fixtures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rt2Xi6TdBDBAc3mhYZAb9q
* test(querier): add coverage for filter/function/aggregation gaps
hasToken + has/hasToken misuse errors, ILIKE/NOT LIKE/NOT CONTAINS,
key:number, key-not-found (logs+traces), min/max, HAVING, metric reduceTo.
hasAny/hasAll over body arrays are marked xfail: they 500 in legacy body
mode (ClickHouse type error) and work only with use_json_body on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rt2Xi6TdBDBAc3mhYZAb9q
* test(querier_json_body): add hasAny/hasAll positive coverage
Body-JSON array functions hasAny/hasAll (string + numeric arrays) succeed in
JSON-body mode (body_v2 -> ClickHouse Array via dynamicElement), complementing
the legacy-mode xfail in querierlogs/09. Verified on ClickHouse 25.12.5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rt2Xi6TdBDBAc3mhYZAb9q
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(dashboards): render logs/traces list panels on public dashboards
Public list panels rendered nothing even though the query returned data:
ListPanelWrapper bails with an empty fragment when setRequestData is
undefined, and the public Panel never forwarded one.
Back requestData with state and forward the setter to WidgetGraphComponent,
mirroring the authenticated GridCard. The per-panel request builder is
extracted to utils.ts.
FixesSigNoz/engineering-pod#3646
* fix(dashboards): hide pagination controls on public list panels
Public widget data is fetched by index and the payload redacts each
widget's limit, so LogsPanelComponent/TracesTableComponent always showed a
pager that can't page (the endpoint takes no pagination params). Thread an
optional hidePagination flag from the public Panel through
WidgetGraphComponent -> PanelWrapper -> ListPanelWrapper to both list
components; default off, so authenticated dashboards are unchanged.
* fix(dashboards): guard against missing orderBy in useLogsData
Public dashboards redact the widget query (orderBy/filter/limit are
stripped), so a LIST query can reach useLogsData with no orderBy. The
optional chain guarded listQuery but not orderBy, so listQuery?.orderBy.find
threw once the public logs panel started rendering. Guard orderBy with ?..
* feat(dashboard-v2): patch builders to apply/sync a dynamic variable's filter across panels
* feat(dashboard-v2): two-column variables table with dynamic apply-to-panels
* fix(dashboard-v2): normalize dynamic variable signal so the source field always shows
* fix(dashboard-v2): keep field-key options during search refetch (smooth typing)
* fix(dashboard-v2): center and show the default-value clear icon
* fix(dashboard-v2): keep the add-variable + on one line when the bar is collapsed
* fix(dashboard-v2): variable selection flow correctness (#12013)
* fix(dashboard-v2): correct "ALL" variable selection end-to-end
- Materialize a query/custom ALL selection to the full option array so it reaches
the query payload (was stored as value:null → dropped); tracks option growth.
- Default a multi-select allow-ALL variable (and the __ALL__ sentinel) to ALL,
matching V1.
- ValueSelector hands CustomMultiSelect the expanded option set when all-selected,
so it no longer renders a literal __ALL__ row.
- Keep a still-valid multi-select subset when options re-scope (was collapsing to
the first option).
- Fix the dead configuredDefault fallback (read defaultValue as string|string[]).
* fix(dashboard-v2): variable selector robustness
- Surface option-fetch errors with a retry in the variable bar (Query/Dynamic) —
a first-fetch failure is no longer a silent, stuck-empty selector.
- Custom variables now auto-select their default/first option (via a CustomSelector
that runs useAutoSelect) instead of rendering blank.
* fix(dashboard-v2): variable fetch-engine correctness
- Ignore a stale/late fetch settle for a variable no longer actively fetching
(restores V1's active-state guard).
- Unblock a waiting query child only once ALL its parents are settled (diamond
dependencies no longer fetch against a stale parent).
- A value change no longer refetches the changed variable's own options, and only
a dynamic change refreshes the other dynamics (query/custom/text changes don't
touch dynamics — they don't depend on those values).
- Enqueue cycle-dropped query variables as best-effort roots instead of leaving
them (and any waiting dynamics) silently stuck.
* fix(dashboard-v2): variable selection edge cases
- Read the __ALL__ URL sentinel as "ALL" only for variables that support it, so a
literal "__ALL__" value (e.g. a text variable) isn't misread.
- Prune URL selections for variables that no longer exist (rename/remove), so a
shared link can't leak a stale value into a later variable.
- Bound option-query cache churn with a cacheTime on the query/dynamic fetches.
* fix(dashboard-v2): render a newly-created empty titled section instead of the dashboard empty state
* feat(dashboard-v2): clone a section with all its panels
* fix(dashboard-v2): disable panel drilldown for non-builder queries
Drilldown was armed whenever the panel had at least one builder query,
even for PromQL / ClickHouse panels (a mixed CompositeQuery, or the
QUERY_BUILDER fallback of deriveQueryType). Those modes carry no builder
context to refine, so the click produced an empty/invalid drilldown.
Gate enableDrillDown on getPanelQueryType(panel) === QUERY_BUILDER in
addition to the existing capability + builder-query-count checks.
* fix(dashboard-v2): hide "Click to drilldown" hint when drilldown is disarmed
TooltipFooter's canDrilldown defaults to true, and the time-series / bar
renderers never passed it — so the hint showed even in the panel editor
preview (where drilldown is off) and for non-builder query types. Passing
the prop as-is wasn't enough: enableDrillDown is undefined in edit mode, and
canDrilldown={undefined} falls back to the true default.
Pass canDrilldown={!!enableDrillDown} (coerced) and add enableDrillDown to
the useCallback deps (also fixes a stale-closure in the bar renderer). The
hint now shows only when a click will actually open the drilldown menu.
* fix(dashboard-v2): make pie panel drilldown fire
preparePieData built slices with only label/value/color, never the
queryName and labels that enrichPieClick needs. Every slice therefore had
queryName === undefined, so enrichPieClick hit isValidQueryName('') and
returned null — the slice click was a silent no-op and pie drilldown never
worked.
Carry the value column's queryName and the row's group-by key→value labels
(keyed by field name) onto each slice, mirroring how enrichTableClick reads
them off the shared PanelTable shape. Adds regression tests for both.
* fix(dashboard-v2): one-click panel create when the dashboard has one section
The New Panel modal always used a select-then-confirm flow with a footer
section picker. When the dashboard has a single section there is nothing to
pick, so the footer degraded to a lone confirm button and the extra click
was pointless.
Hide the footer entirely for the single-section case and create the panel
directly on tile click; keep select-then-confirm when multiple sections
exist. The section-picker guard moves up to the modal so the footer renders
only when a picker is actually needed.
* fix(dashboard-v2): cap the variable preview-values box height
The 'Preview of Values' box only had a min-height, so a field with many values
grew the box tall. Add a max-height so it scrolls instead.
* feat(dashboard-v2): restore the V1 combined field/source row for dynamic variables
Replace the separate Source and Attribute rows with the single V1-style row:
the telemetry field on the left, then "from" and the source select on the right.
* feat(dashboard-v2): add-variable button in the variables bar
Add a dashed outlined 'Add variable' button on the left of the variables bar
(shown for editors even with no variables). It deep-links into the settings
drawer at the Variables tab with the add-variable form open, via a transient
settingsRequest store slice; the drawer destroys on close so it re-initializes
cleanly each open.
* feat(dashboard-v2): collapse the add-variable button once variables exist
Full labelled button when there are no variables; once some exist it shrinks to
a + icon (label on hover) placed after the collapsed +N. Dashed l3 border.
* style(dashboard-v2): use the l3 border on toolbar buttons and the empty state
Give Actions/Configure/Edit-as-JSON and the dashboard empty-state dashed border
the more visible --l3-border.
* fix(dashboard-v2): keep add-variable button out of collapsed bar + flat toolbar borders
- Move the add-variable + icon inside the strip and only show it when the bar
isn't collapsed (no overflow) or is expanded, so it never breaks the layout
mid-wrap; expand to reveal it.
- Make the Actions/Configure/Edit-as-JSON border a flat l3 outline with no
box-shadow/depth.
* feat(dashboard-v2): raise dynamic-variable attribute search debounce to 500ms
* feat(dashboard-v2): add a Configure step to the dashboard empty state
* fix(dashboard-v2): flow the variables bar under the time selector + reposition add button
Revert the bar wrapper to block so the strip wraps around the floated time
selector (one line beside it collapsed; full-width below it when expanded) — a
flex wrapper had made it a BFC that trapped the rows on the left. The add-variable
"+" now sits after the +N/Less trigger, kept inline and always mounted so the
overflow measurement no longer toggles it (which caused a layout shift), with the
collapse math reserving space for it.
* feat(dashboard-v2): hide dashboard chrome in full screen
Show only the sections and panels in full screen — the header and toolbar
(actions, variables bar, time selector) are hidden for a clean presentation
view; exit with Esc. Also drops a stray blank line that failed the format check.
* refactor(dashboard-v2): extract add-variable buttons into components
* feat(dashboard-v2): lock-aware edit context, mutation guard, tooltip primitives & locked indicator, skip chart refetch on lock toggle
* feat(dashboard-v2): hide toolbar edit actions (incl. clone) in view mode, disable with a hover reason when locked
* feat(dashboard-v2): hide panel & section edit actions in view mode, disable when locked
* feat(dashboard-v2): open the JSON editor read-only when not editable
* feat(dashboard-v2): disable panel editor save when not editable
* feat(trace-details): restyle Pretty View attributes search bar
* feat(trace-details): full-width row hover in Pretty View
* feat(trace-details): keep Pretty View row active and brighten value on hover/menu-open
* feat(trace-details): move parent-row ellipsis to the right edge
* fix(cmdk): raise command palette above the floating span-details panel
* feat: use toggle group for data viewer instead of dropdown
* fix(trace-details): disable Monaco sticky scroll in JSON view to stop overlap
* feat(dashboard-v2): scope panel refetch to referenced variables
A panel now refetches only when a variable its queries reference changes (the cache key is scoped to referenced variables; the full payload is still substituted), and stays in its loading state on first load until those variables resolve.
* feat(dashboard-v2): surface dashboard variables in query builder suggestions
Publish the dashboard's variables into the shared store the query builder autocomplete reads, so $variable suggestions appear in the dashboards-page builder and the panel editor.
* feat(dashboard-v2): add extend-time-window logic for empty panels
Add the empty-state "extend the time window" building block: useExtendTimeWindow
widens the dashboard's global time via the shared zoom-out ladder (getNextZoomOutRange
/ useZoomOut), and extendWindow.ts exposes the ExtendTimeWindow type plus a thin
buildExtendWindow adapter over the ladder result.
* feat(dashboard-v2): add view-session store slice for the modal extender
The View modal owns a local time window; add a transient dashboard-store
slice so it can publish its extend-window behaviour for the deeply-nested
NoData empty state to read, instead of threading it through every renderer.
* feat(dashboard-v2): offer an extend-window action in the panel no-data state
A panel with no data in the selected window showed only a Retry, which can't
help when the query succeeded and there's just nothing in range. Add an
"Extend time range" action that widens the window (global time on the
grid/editor; the View modal's local window in place, read from the store),
shown alongside the retained Retry.
Also fix the stale empty state: keepPreviousData retained the empty response so
NoData lingered through a refetch then snapped to the chart — a refetch over
empty data now shows the shared PanelLoader instead.
PanelMessage gains a secondaryAction so the empty state can render both buttons;
refetch stays on the renderer contract to drive Retry (standalone call sites
like the editor preview may still omit it, showing Extend only).
* chore: pr review fixes
* chore: pr review changes
* chore: pr review changes
* feat(dashboard-v2): add runtime variable fetch-state engine
Port V1's variable fetch orchestration natively onto the dashboard store: a fetch-state slice (idle/loading/revalidating/waiting/error + per-variable cycle ids) plus the dependency context derivation (query/dynamic order) needed to schedule fetches. Query variables load in dependency order; dynamics wait for query values. enqueueFetchAll drives first load/time change, enqueueDescendants drives a single value change.
* feat(dashboard-v2): drive variable selectors off the fetch engine
Gate the Query/Dynamic option fetches on the engine's per-variable state and key them by cycle id (so a parent change refetches only its dependents, in order, with no duplicate calls), and report completion/failure back. Wire the selection lifecycle: full fetch cycle on load/time change, descendant cascade on value change.
* fix(dashboard-v2): read a variable's default value as string, not { value }
defaultValue is a string | string[] on the wire, but the editor and runtime read
it as { value }, so a saved default showed blank in the form (and couldn't be
cleared) and never auto-applied. Read it directly in all three sites.
* feat(dashboard-v2): show hidden variable values on the collapsed +N hover
When the variables bar collapses the overflow behind +N, hovering it now lists
the hidden variables and their current selected values.
* feat(dashboard-v2): batch initial variable auto-selections into one fetch
Auto-selection now flows through a dedicated onAutoSelect path, separate from
user changes. The initial load burst of fills (each selector picks a value as
its options resolve, at different times) is coalesced across one animation frame
into a single store write plus one enqueueDescendantsBatch, so dependent
variables re-fetch once with the settled parent values instead of once per fill.
* refactor(dashboard-v2): use enum for VariableFetchState
* chore(dashboards-v2): add html-to-image for panel image export
Dependency used to capture a panel's rendered DOM node as PNG/SVG.
* feat(dashboards-v2): declare per-kind download capability
Adds the DownloadFormat enum and a per-kind `actions.download` map. Tables allow CSV/PNG/SVG, charts PNG/SVG; CSV is table-only (V1 parity).
* feat(dashboards-v2): capture a panel as a PNG/SVG image
downloadElementAsImage captures the panel's rendered node via html-to-image (dropping the hover chrome); useDownloadPanelImage locates it by its data-panel-root marker and surfaces failures as a toast. toSafeFileName sanitizes the filename.
* feat(dashboards-v2): export table panel data as CSV
getTableCsvRows flattens the prepared scalar table (reusing the on-screen cell formatting) into rows, and downloadCsv serializes them via papaparse.
* feat(dashboards-v2): add the Download action to the panel menu
useDownloadPanelMenuItem composes the submenu, dispatching CSV to the query response (useDownloadPanelCsv) and PNG/SVG to the DOM capture; the response is threaded to the menu via props. buildDownloadMenuItem renders the format options; usePanelActionItems consumes the ready item.
* chore: pr review fixes
* feat(llm-pricing): add model pricing foundation (route, permission, page shell)
* feat(llm-pricing): add listing page and table
* chore(llm-pricing): drop search + source filters from list request
The list API does not honour the q (search) and source params yet, so
the controls did nothing. Remove the search input and source dropdown
along with the params we sent, and trim useModelPricingFilters to the
URL-backed page state that pagination still needs. Currency dropdown,
tabs, table and pagination are unchanged. Filters will return once the
backend supports them.
* refactor(llm-pricing): extract getRelativeTime helper in utils
Pull the relative-time formatting out of getRelativeLastSeen into a
small local getRelativeTime helper. Kept feature-local (not in the
shared utils/timeUtils) so the LLM pricing module owns its own dayjs
config; the local relativeTime extend stays for test self-sufficiency.
* refactor(llm-pricing): drop dead NaN guard in formatPricePerMillion
Pricing fields are typed as required numbers and JSON can't carry NaN,
so Number.isNaN was unreachable. Keep the null/undefined guard as API
defensiveness (toFixed on a missing value would crash the row). Also
trims the now-redundant dayjs.extend comment.
* refactor(llm-pricing): centralize constants and shared types
Extract PAGE_SIZE, PAGE_KEY, COLUMN_COUNT and CURRENCY_OPTIONS into a
new constants.ts, and move the ModelPricingFilters contract into
types.ts. Component prop interfaces stay colocated with their
components, matching the convention in the drawer PR.
* refactor(llm-pricing): use nuqs for list pagination URL state
Replace the hand-rolled useHistory + URLSearchParams plumbing in
useModelPricingFilters with nuqs useQueryState, matching the convention
used by the dashboards, alerts and k8s list pages. Behaviour is
unchanged: parseAsInteger.withDefault(1) keeps ?page=1 out of the URL
and history:'replace' avoids polluting the back-stack.
* refactor(llm-pricing): inline pagination, drop useModelPricingFilters
The hook had shrunk to a one-line nuqs wrapper after search/source were
removed, so inline the useQueryState call into the container and remove
the hook file plus the now-unused ModelPricingFilters type. When the
filters return (once the API honours them) they can move back into a
dedicated hook.
* feat(llm-pricing): disable currency selector (USD-only for now)
Only USD is priced today, so render the currency SelectSimple in a
disabled state pinned to USD. A disabled select can't fire onChange, so
the currency useState is dead — drop it (and the now-unused useState
import).
* refactor(llm-pricing): render model costs inside its tab + tab URL param
The listing was rendered outside the Tabs, so the tab was decorative.
Move all model-cost content (currency control, list query, table,
pagination, footer) into a ModelCostsTab component rendered as the
'Model costs' tab's children, and drive the active tab from a 'tab' URL
query param (nuqs). The container is now just the page shell. Unpriced
models stays a disabled placeholder for a later PR.
* style(llm-pricing): target @signozhq table slots, drop dead antd/leftover rules
The component uses @signozhq/ui Table/Tabs (Radix-based), not antd, so the
.ant-table-* and .ant-tabs-nav selectors never matched — the intended
uppercase/muted header styling wasn't applied. Retarget header/cell rules to
[data-slot='table-head'|'table-cell'] (no !important needed). Also remove dead
rules left over from the removed search/source/add UI (.filters-bar__search,
__source, __add, .page-header__actions) and the unused .source-badge--auto/
--override modifiers.
* fix(llm-pricing): constrain currency dropdown width, drop tab URL param
- Currency SelectSimple stretched to fill the filters bar; give it a fixed
160px width (min-width couldn't cap the trigger).
- Model costs is the only enabled tab for now, so use Tabs defaultValue
instead of a URL-backed param. Removes the nuqs tab state plus the now-unused
TAB_KEYS/TAB_QUERY_KEY constants and TabKey type.
* chore: self review changes
* fix: add skeleton loading
* refactor: self review changes
* refactor: initial prop
* fix: update styling
* fix: add comments in utils
* feat(llm-pricing): add model cost drawer and wire into listing page
* fix(llm-pricing): restrict pricing management to admins
Align the frontend write gate with the backend, which protects the
LLM pricing create/update/delete endpoints with AdminAccess (admin
only). Previously manage_llm_pricing allowed EDITOR/AUTHOR, so those
roles saw the Add/Save affordances but their writes were rejected with
a 403. Also removes the AUTHOR entry, which could never reach the page
(the route gate excludes it).
* fix(llm-pricing): read-only drawer shows View title, hides source picker
Non-managers open the drawer in view mode (write APIs are Admin-only), so:
- the heading reads "View model cost" instead of "Edit model cost"
- the Source (auto vs. override) picker is hidden, since switching source is
a manager-only action with nothing actionable for a viewer.
* refactor: form in edit / add modal
* chore: update color tokens
* fix: add error handling
* chore: update more self review changes
* chore: self review changes
* chore: self review changes
* fix: minor grammer thing
* fix: route thing
* refactor: migrate to css moduel
* refactor: migrate to css module
* refactor: migrate to css module
* refactor: migrate to tanstack table
* docs: clarify price precision comment
* chore: remove comment
* chore: remove comment
* fix: disable isDirty in case of llm pricing
* refactor: number
* feat: add search , dropdown and flag
* feat: feature flag on entire route and add mode costs tabs
* fix: add isFetchingFeatureFlags
* chore: update flag
* refactor: shell
* fix: add key to route
* feat: add flags
* chore: additional refactor
* chore: add commet in utis
* chore: self review changes
* refactor: types and other things
* refactor: types and other things
* chore: add disable on source id
* empty commit
* chore: empty commit
* fix: add demo side nav on sidenav
* chore: remove demo side nav
* refactor: update routes
* chore: remove usd selector for now
* fix: layout shift
* refactor: styles
* refactor: typography component
* refactor: more changes
* refactor: typograhy
* refactor(llm-pricing): break model-cost drawer into per-component files + tokens
Apply the CSS-module/component conventions to the drawer that came from
drawer-3:
- Move the drawer under ModelCostTabPanel/components/ModelCostDrawer/ to mirror
the ModelCostsTable structure
- Split the single 395-LOC ModelCostDrawer.module.scss into per-component
co-located modules; cross-component selectors live in shared.module.scss and
are pulled in via CSS-modules `composes`
- shared.module.scss is a composes target (parsed as plain CSS), so it is kept
flat with block comments — no SCSS nesting or // comments
- Use --text-vanilla-* (not --bg-vanilla-*) for text colors, matching the
listing code
* refactor: more changes
* refactor: styling and components
* refactor: styling and components
* chore: add a tooltip on hover
* feat: add delete confirm modal
* fix: update title
* refactor: css variables
* refactor: use signoz button and minor css update
* chore: sync table
* chore: remove extra comment
* chore: use typograpgy test in table config
* fix: minior issues
* fix: llm pricing listing
* refactor: remove extra classes
* refactor: side nav changes
* fix: update missing styles
* chore: update edit and delete options
* chore: remove extra comment
* chore: revert env changes
* chore: add enable check
* chore: remove divider
* refactor: use delete confirm dialog
* chore: remove scss file
* feat: move ui to easily accessable tabs
* feat: update test cases
* chore: update text
* chore: self review changes
* chore: self review refactor
* chore: self review changes
* chore: remove worktree
* chore: revert env.ts
* chore: update tests
* chore: self review changes
* chore: update test cases
* chore: remove extra comments
* chore: use constants
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
* refactor(trace-details): use ArrowRightFromLine for filter collapse icon
* refactor(trace-details): square header back button with l1 border, tabular-nums trace ID
* refactor(trace-details): show events count as an l3 badge
* feat(periscope): add reusable CopyButton with copy-to-check animation
* refactor(trace-details): use CopyButton in DataViewer and filter query popover
* fix(traces): address PR review on CopyButton and events badge
- Remove unused onCopy prop from CopyButton; it was wired to fire
unconditionally (even on failed clipboard writes), contradicting its
documented "on successful copy" contract, and no caller used it. Will
be re-added gated on success when actually needed.
- Only render the Events tab count badge when eventsCount > 0 so spans
with no events don't show a "0" badge.
* style(traces): tokenize events badge margin per review
Use var(--spacing-3) for the events badge margin-left instead of a raw
6px. Leave the 18px badge box and 5px padding off-grid (no matching
token; nearest values would change the single-digit circle).
* fix(dashboard-v2): only mark a new panel savable once it has a query
A new panel was always treated as dirty, so Save was enabled even with no
query to run. Track dirtiness off spec/query edits and require a seeded query
(List auto-seeds one; other kinds open query-less) before a new panel is
savable.
* feat(dashboard-v2): drill down from the panel View modal
Wire the shared drill-down orchestration into the View modal so filter-by-value
and breakout refine the expanded view in place (URL-persisted, re-runs the
preview) instead of opening a nested View modal. Adds an OpenDrilldownView
handoff type, an optional openDrilldownView override on useDrilldown, and the
PreviewPane onClick/enableDrillDown pass-through the modal uses. Cells and log
rows get a pointer/hover affordance for the click.
* fix(dashboard-v2): keep the dashboard spec cache patch-driven
The spec cache is kept fresh by optimistic patches, so auto-refetching flashed
the grid back to server state as observers mounted across the panel tree. Set
staleTime: Infinity + refetchOnMount: false; explicit refetch() still works.
* feat(dashboard-v2): add "Switch to View Mode" to the panel editor
Add the V1 "Switch to View Mode" button to the panel editor header. It leaves
the full-page editor for the dashboard with this panel expanded in the View
modal, seeded with the live (un-saved) query via the same expandedWidgetId +
graphType + compositeQuery URL contract the modal hydrates from. Shown for
existing panels only — a new panel isn't saved to the dashboard spec yet.
* fix: ui fixes for panel selection modal
* feat(dashboards-v2): choose target section when adding a panel
Add a section picker to the New Panel modal so a panel can be placed in
any section (or the untitled root), not just the one the "Add panel"
action was triggered from. Sections are read from the cached dashboard
query via useDashboardSections (no refetch, no prop-drilling).
Picking a panel type now selects it (highlighted card) and reveals a
footer holding the section dropdown and an "Add Panel" button split
50/50; confirming shows a brief loader before navigating to the editor.
The chosen section's layoutIndex is threaded through
useCreatePanel.createPanel.
* fix(dashboards-v2): refine New Panel modal footer + section labels
- Keep the footer visible at all times; the "Add Panel" confirm is disabled
until a panel type is selected (instead of hiding the whole footer).
- Footer layout: the section selector fills the available width and the
confirm button takes its natural width (no more 50/50 split).
- Add a "Select panel type" label above the panel-type grid, matching the
existing "Add panel to" label.
* chore: pr review fixes
* feat: add resizable quick filters panel with persistent width settings
* feat: add tooltip to checkbox value display for better user experience
* feat: add grip handle to resizable box for improved user interaction
* feat(dashboard-v2): linkify URLs in dashboard description tooltip
Parse http(s) and www. links in the dashboard description and render
them as clickable anchors opening in a new tab. Add a reusable
linkifyText util under src/utils. Preserve author line breaks and show
the tooltip below the info icon.
* feat(dashboard): linkify URLs in V1 dashboard description
Apply the linkifyText util to the V1 dashboard description section so
pasted URLs become clickable, matching the V2 behaviour. Preserve line
breaks and wrap long URLs.
* fix(dashboard-v2): surface the real API error when a JSON save fails
updateDashboardV2 throws the raw generated error; it was passed to the error
modal cast as APIError, so a rejected save (e.g. a locked dashboard) rendered no
message. Run it through toAPIError so the actual error is shown.
* feat(dashboard-v2): warn when JSON edits desync panels and layouts
Editing the dashboard JSON can leave a panel in spec.panels that no layout
places (orphaned — renders nowhere), or a layout item referencing a panel that
no longer exists. Detect both from the draft (findPanelLayoutIssues, typed off
the spec DTO) and show a footer warning with a triangle icon; hovering lists the
exact panel ids. The tooltip sits above the drawer.
* fix(dashboard-v2): keep editor keystrokes from triggering global shortcuts
Stop keydown propagation out of the JSON drawer so Shift-selection and typing in
the editor don't fire app-level keyboard shortcuts.
* feat(dashboard-v2): rename saved views via a shared 128-char name popover
Add a per-row rename action wired to renameView() (existing PUT endpoint), and
extract the save/rename name input into a shared ViewNamePopover capped at 128
chars (replaces SaveViewPopover). Group the rename/delete row actions so they no
longer overlap, patch the renamed view into the list cache inline instead of
refetching, and make the active-view 'Reset' restore the view's opening filters.
* feat(dashboard-v2): show locked = true in the query for the Locked view
The Locked built-in view applied its filter as an invisible server-only clause;
seed it into the search box instead so the constraint is visible and editable,
and drop the now-unused viewQuery plumbing.
* feat(dashboard-v2): source the created-by filter from the org user list
The creator filter only listed authors present on the loaded page, so a user who
hadn't authored a visible dashboard couldn't be selected. Source options from the
v2 List-users API via useCreatorOptions (page authors kept as a fallback until it
resolves), labelled with the user's display name.
* feat(dashboard-v2): show the unsaved-changes dot on the results header
Mirror the views rail's dirty indicator next to the active view name in the
results header when the current filters diverge from the view.
A dashboard with no description has no /spec/display/description path, so a
JSON-Patch replace failed and the description update silently no-op-d. Use add
(create-or-replace) when the current description is empty.
* chore: added types and open api spec changes
* chore: added method to calculate reason
* chore: per group pod status counts with req metric checks method added
* chore: wired up pod status counts
* chore: pod restarts type added
* chore: added restart counts for the group
* chore: bug in query fix
* chore: onboarding API changes
* chore: integration tests added
* chore: added podcountsbyphase in other entities
* chore: added pod status counts for other entities
* chore: added integration tests for other entities
* chore: added checks api changes for other entities
* chore: rearrangement
* chore: removed succeeded status and mark it as completed
* chore: query beautified
* chore: corrected metrics list for metadata lookup
* chore: removed dead constants
* chore: goroutines for ListHosts
* chore: goroutines for ListPods
* chore: goroutines for ListNodes
* chore: goroutines for ListNamespaces
* chore: goroutines for ListClusters
* chore: goroutines for ListDeployments
* chore: goroutines for ListStatefulsets
* chore: added goroutines for ListStatefulsets, ListJobs and ListDaemonsets
* chore: added types and open api spec changes
* chore: added method to calculate reason
* chore: per group pod status counts with req metric checks method added
* chore: wired up pod status counts
* chore: pod restarts type added
* chore: added restart counts for the group
* chore: bug in query fix
* chore: onboarding API changes
* chore: integration tests added
* chore: added podcountsbyphase in other entities
* chore: added pod status counts for other entities
* chore: added integration tests for other entities
* chore: added checks api changes for other entities
* chore: rearrangement
* chore: removed succeeded status and mark it as completed
* chore: query beautified
* chore: corrected metrics list for metadata lookup
* chore: removed dead constants
SigNoz uses [OpenFGA](https://openfga.dev/), a relationship-based access control (ReBAC) system, to authorize every request. Transactions are never attached to users directly — they are attached to **roles** (as relationship tuples in OpenFGA), and users or service accounts (principals) are made **assignees** of those roles. The central interface is `AuthZ` in [pkg/authz/authz.go](/pkg/authz/authz.go), backed by an embedded OpenFGA server in [pkg/authz/openfgaserver](/pkg/authz/openfgaserver/server.go).
As a feature author, you will rarely touch OpenFGA directly. You interact with two layers:
1.**The registries** in [pkg/types/coretypes](/pkg/types/coretypes) — where every resource, verb, and managed-role permission is declared in code.
2.**The route wiring** in [pkg/apiserver/signozapiserver](/pkg/apiserver/signozapiserver) — where each route declares what resource it touches and which verb it needs, and the middleware turns that into a check.
## What are the building blocks?
### Type
A `Type` is the coarse FGA type of a resource. The set is finite and defined in [pkg/types/coretypes/registry_type.go](/pkg/types/coretypes/registry_type.go): `user`, `serviceaccount`, `anonymous`, `role`, `organization`, `metaresource`, and `telemetryresource`. Each type carries a selector regex (what a valid ID looks like) and the verbs allowed on it.
Almost every feature resource is a `metaresource` (dashboards, rules, pipelines, ...) or a `telemetryresource` (logs, traces, metrics). You should almost never need a new type.
### Verb
A `Verb` is the action being authorized. All verbs are defined in [pkg/types/coretypes/registry_verb.go](/pkg/types/coretypes/registry_verb.go): `create`, `read`, `update`, `delete`, `list`, `assignee`, `attach`, and `detach`.
-`assignee` is special: it is the membership relation between a subject and a role ("user X is an assignee of role Y"), not an action a route checks for.
-`attach`/`detach` authorize linking two resources together (e.g. assigning a role to a service account).
### Kind
A `Kind` is the fine-grained name of your resource — `dashboard`, `rule`, `quick-filter`. Kinds are registered in [pkg/types/coretypes/registry_kind.go](/pkg/types/coretypes/registry_kind.go). A kind rides on top of an existing type, so adding one does **not** require any OpenFGA schema change.
### Resource
A `Resource` ties a type and a kind together and knows how to render itself as an FGA object string. The interface lives in [pkg/types/coretypes/resource.go](/pkg/types/coretypes/resource.go):
All resources are registered in [pkg/types/coretypes/registry_resource.go](/pkg/types/coretypes/registry_resource.go) using constructors like `NewResourceMetaResource(KindDashboard)` or `NewResourceTelemetryResource(KindLogs)`.
### Selector
A `Selector` identifies *which* instance(s) of a resource a check is about — a UUID, a role name, or the wildcard `*`. A `SelectorFunc` maps the extracted resource ID to selectors at request time. Two prebuilt ones cover most routes ([pkg/types/coretypes/selector.go](/pkg/types/coretypes/selector.go)):
-`WildcardSelector` — the check is against all instances of the resource (`create`, `list`).
-`IDSelector` — the check is against the specific instance *or* the wildcard (`read`, `update`, `delete` of one object). A subject authorized on `*` is authorized on every instance.
When the ID in the request is not what FGA needs (e.g. routes receive a role UUID but FGA objects use role names), write a custom `SelectorFunc` — see `roleSelector` in [pkg/apiserver/signozapiserver/serviceaccount.go](/pkg/apiserver/signozapiserver/serviceaccount.go).
### Roles, transactions, and tuples
SigNoz ships four managed roles, declared in [pkg/types/coretypes/registry_managed_role.go](/pkg/types/coretypes/registry_managed_role.go): `signoz-admin`, `signoz-editor`, `signoz-viewer`, and `signoz-anonymous`. Their permissions are declared in code as `Transaction`s (a verb on an object) in `ManagedRoleToTransactions` — this map is the single source of truth for what each managed role can do.
At organization bootstrap, `CreateManagedRoles` and `CreateManagedUserRoleTransactions` (see [pkg/authz/authz.go](/pkg/authz/authz.go)) persist the role rows and write one OpenFGA tuple per transaction, linking `role:organization/<orgID>/role/<name>#assignee` to each permitted object. Users and service accounts are then granted roles via `Grant`/`Revoke`, which writes `assignee` tuples. Custom roles (enterprise) are managed through the roles API in [pkg/authz/signozauthzapi/handler.go](/pkg/authz/signozauthzapi/handler.go).
### Schema
The OpenFGA authorization model is a hand-written DSL, embedded at build time: [pkg/authz/openfgaschema/base.fga](/pkg/authz/openfgaschema/base.fga) for community and [ee/authz/openfgaschema/base.fga](/ee/authz/openfgaschema/base.fga) for enterprise. The community model only supports role assignment; the enterprise model defines per-verb relations on every type, enabling genuine per-resource checks. This split is why `CheckWithTupleCreation` behaves differently per edition (see [How does a check work at runtime?](#how-does-a-check-work-at-runtime)). You only touch these files when introducing a brand-new **type** — never for a new kind.
## How do I add authz to my feature?
### 1. Register the kind
Add your kind in [pkg/types/coretypes/registry_kind.go](/pkg/types/coretypes/registry_kind.go) and append it to `Kinds`:
```go
KindThing=MustNewKind("thing")
```
### 2. Register the resource
Add the resource in [pkg/types/coretypes/registry_resource.go](/pkg/types/coretypes/registry_resource.go) and append it to `Resources`:
Pass an explicit verb list to `NewResourceMetaResource` only if your resource supports fewer verbs than the type default.
### 3. Grant permissions to managed roles
Decide what each managed role can do with your resource and add the transactions in [pkg/types/coretypes/registry_managed_role.go](/pkg/types/coretypes/registry_managed_role.go):
The convention so far: admin gets everything, editor gets CRUD on day-to-day observability resources, viewer gets `read`/`list`, anonymous gets nothing (except public dashboards).
### 4. Wire the route
Register the route in [pkg/apiserver/signozapiserver](/pkg/apiserver/signozapiserver), wrapping your handler with `CheckResources` and declaring what the route touches via a `ResourceDef` ([pkg/http/handler/resourcedef.go](/pkg/http/handler/resourcedef.go)). A complete example from [pkg/apiserver/signozapiserver/serviceaccount.go](/pkg/apiserver/signozapiserver/serviceaccount.go):
- **`CheckResources(handlerFn, roles...)`** — the resource-aware authorization wrapper from [pkg/http/middleware/authz.go](/pkg/http/middleware/authz.go). The role list is the community-edition fallback: which managed roles may call this route when per-resource checks are unavailable.
- **`ResourceDef`** — declares the resource, verb, audit category, how to extract the instance ID, and how to turn that ID into selectors. ID extractors live in [pkg/types/coretypes/extractor.go](/pkg/types/coretypes/extractor.go): `PathParam("id")`, `BodyJSONPath("data.id")`, `BodyJSONArray("ids")`, and `ResponseJSONPath("data.id")` for IDs only known after the handler runs (e.g. `create`).
- **`SecuritySchemes`** — advertises the required scope (`resource.Scope(verb)`, e.g. `serviceaccount:create`) in the OpenAPI spec.
For routes that link two resources, use `AttachDetachSiblingResourceDef` (both sides are authz-checked, e.g. attaching a role to a service account requires `attach` on **both** the service account and the role). For parent-child routes (e.g. creating an API key under a service account), both sides are checked too, but with different verbs: declare a `BasicResourceDef` checking the child with `create`/`delete`, alongside an `AttachDetachParentChildResourceDef` checking the parent with `attach`/`detach` (within that def the child is only recorded for audit) — see the `/api/v1/service_accounts/{id}/keys` route in [pkg/apiserver/signozapiserver/serviceaccount.go](/pkg/apiserver/signozapiserver/serviceaccount.go).
Prefer `CheckResources` with a `ResourceDef` for anything resource-shaped. The older coarse gates `ViewAccess`/`EditAccess`/`AdminAccess` only check "does the caller hold one of these roles" and give up per-resource granularity; `OpenAccess` performs no authorization (authentication still applies); `CheckWithoutClaims` serves anonymous routes such as public dashboards.
### 5. Backfill existing organizations (only if needed)
Managed-role tuples are written from the registry at organization creation, so **new resources need no migration for new organizations**. If existing organizations must get the new permissions, add a migration in [pkg/sqlmigration](/pkg/sqlmigration) that inserts the tuples — see [083_add_role_crud_tuples.go](/pkg/sqlmigration/083_add_role_crud_tuples.go) for the pattern.
## How does a check work at runtime?
1. The resource middleware ([pkg/http/middleware/resource.go](/pkg/http/middleware/resource.go)) runs on every request. It reads the matched handler's `ResourceDef`s, extracts the resource IDs from path/body, and stores the resolved resources in the request context.
2.`CheckResources` reads them back, runs each `SelectorFunc`, and calls `AuthZ.CheckWithTupleCreation(ctx, claims, orgID, relation, resource, selectors, roleSelectors)`.
3. What happens next depends on the edition:
- **Community** ([pkg/authz/openfgaserver/server.go](/pkg/authz/openfgaserver/server.go)) ignores the resource and selectors and only checks whether the subject is an `assignee` of one of the allowed roles — a plain role gate.
- **Enterprise** ([ee/authz/openfgaserver/server.go](/ee/authz/openfgaserver/server.go)) builds tuples via `authtypes.NewTuples` — subject `user:organization/<orgID>/user/<userID>`, relation `create`, object `serviceaccount:organization/<orgID>/serviceaccount/*` — and batch-checks them against OpenFGA: genuine per-resource authorization, including custom roles.
Because both paths go through the same middleware and the same `ResourceDef` declarations, a route wired once works correctly in both editions.
## What should I remember?
- Declare authz in the registries ([pkg/types/coretypes](/pkg/types/coretypes)), not in migrations — tuples for new organizations are derived from code at bootstrap.
- A new kind never needs an OpenFGA schema change; only a new type does, and then **both** [pkg/authz/openfgaschema/base.fga](/pkg/authz/openfgaschema/base.fga) and [ee/authz/openfgaschema/base.fga](/ee/authz/openfgaschema/base.fga) must be updated together.
- Prefer `CheckResources` + `ResourceDef` over the coarse `ViewAccess`/`EditAccess`/`AdminAccess` gates for new routes.
- Use `WildcardSelector` for `create`/`list`, `IDSelector` for instance operations, and a custom `SelectorFunc` when the request ID is not the FGA selector.
- Linking routes check **both** sides: peers via `AttachDetachSiblingResourceDef` (same verb on both), parent-child via a `BasicResourceDef` on the child (`create`/`delete`) paired with an `AttachDetachParentChildResourceDef` on the parent (`attach`/`detach`).
- Changing `ManagedRoleToTransactions` only affects organizations created afterwards — add a [pkg/sqlmigration](/pkg/sqlmigration) migration to backfill existing ones.
@@ -11,6 +11,7 @@ We adhere to three primary style guides as our foundation:
We **recommend** (almost enforce) reviewing these guides before contributing to the codebase. They provide valuable insights into writing idiomatic Go code and will help you understand our approach to backend development. In addition, we have a few additional rules that make certain areas stricter than the above which can be found in area-specific files in this package:
- [Abstractions](abstractions.md) - When to introduce new types and intermediate representations
- [Authz](authz.md) - Authorization, roles, and access control
returnerrors.New(errors.TypeLicenseUnavailable,errors.CodeLicenseUnavailable,"a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
returnnil,errors.New(errors.TypeLicenseUnavailable,errors.CodeLicenseUnavailable,"a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
returnnil,errors.New(errors.TypeLicenseUnavailable,errors.CodeLicenseUnavailable,"a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
returnerrors.New(errors.TypeLicenseUnavailable,errors.CodeLicenseUnavailable,"a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
returnerrors.New(errors.TypeLicenseUnavailable,errors.CodeLicenseUnavailable,"a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
// functional partial indexes aren't representable (PartialUniqueIndex has no
// expressions); skip rather than misrepresent.
ifentry.hasExpression&&entry.predicate!=nil{
provider.settings.Logger().WarnContext(ctx,"skipping functional partial unique index; not representable by sqlschema",slog.String("index",indexName),slog.String("table",string(name)))
continue
}
ifentry.predicate!=nil{
index:=&sqlschema.PartialUniqueIndex{
TableName:name,
@@ -291,6 +327,17 @@ ORDER BY index_name, column_position`, string(name))
* Returns a paginated list of Kubernetes DaemonSets with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the daemonset, plus average CPU/memory request and limit utilization (daemonSetCPURequest, daemonSetCPULimit, daemonSetMemoryRequest, daemonSetMemoryLimit). Each row also reports the latest known node-level counters from kube-state-metrics: desiredNodes (k8s.daemonset.desired_scheduled_nodes, the number of nodes the daemonset wants to run on) and currentNodes (k8s.daemonset.current_scheduled_nodes, the number of nodes the daemonset currently runs on) — note these are node counts, not pod counts. It also reports per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value). Each daemonset includes metadata attributes (k8s.daemonset.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.daemonset.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by daemonsets in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_nodes / current_nodes, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (daemonSetCPU, daemonSetCPURequest, daemonSetCPULimit, daemonSetMemory, daemonSetMemoryRequest, daemonSetMemoryLimit, desiredNodes, currentNodes) return -1 as a sentinel when no data is available for that field.
* Returns a paginated list of Kubernetes DaemonSets with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the daemonset, plus average CPU/memory request and limit utilization (daemonSetCPURequest, daemonSetCPULimit, daemonSetMemoryRequest, daemonSetMemoryLimit). Each row also reports the latest known node-level counters from kube-state-metrics: desiredNodes (k8s.daemonset.desired_scheduled_nodes, the number of nodes the daemonset wants to run on), currentNodes (k8s.daemonset.current_scheduled_nodes, the number of nodes the daemonset currently runs on), readyNodes (k8s.daemonset.ready_nodes, the number of nodes running at least one ready daemon pod) and misscheduledNodes (k8s.daemonset.misscheduled_nodes, the number of nodes running the daemon pod but not supposed to) — note these are node counts, not pod counts. It also reports per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value). Each daemonset includes metadata attributes (k8s.daemonset.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.daemonset.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by daemonsets in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_nodes / current_nodes / ready_nodes / misscheduled_nodes, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (daemonSetCPU, daemonSetCPURequest, daemonSetCPULimit, daemonSetMemory, daemonSetMemoryRequest, daemonSetMemoryLimit, desiredNodes, currentNodes, readyNodes, misscheduledNodes) return -1 as a sentinel when no data is available for that field.
* Returns a paginated list of Kubernetes containers with key kubeletstats metrics: CPU usage (cores), CPU request/limit utilization, memory working set, and memory request/limit utilization. Each container also reports health signals from the k8s_cluster receiver: status (kubectl-style display status derived from k8s.container.status.state + k8s.container.status.reason), restarts (absolute count from k8s.container.restarts), and ready (ready/not_ready from k8s.container.ready). The row identity is (k8s.pod.uid, k8s.container.name), stable across container restarts. Each container includes metadata attributes (k8s.container.name, k8s.pod.name, container.image.name, container.image.tag, k8s.namespace.name, k8s.node.name, k8s.cluster.name, and workload owner such as deployment/statefulset/daemonset/job). The response type is 'list' for the default (k8s.pod.uid, k8s.container.name) grouping (each row is one container with its current status and ready state) or 'grouped_list' for custom groupBy keys (each row aggregates containers in the group with per-status counts under containerCountsByStatus, per-readiness counts under containerCountsByReady, and restarts as the group sum). Status requires the optional k8s.container.status.state and k8s.container.status.reason metrics; when either is missing, status is omitted and a warning is returned while restarts and ready are still computed. Supports filtering via a filter expression, custom groupBy, ordering by any of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (cpu, cpuRequestUtilization, cpuLimitUtilization, memory, memoryRequestUtilization, memoryLimitUtilization) and restarts return -1 as a sentinel when no data is available for that field.
* @summary List Kubernetes Containers for Infra Monitoring
* Returns a paginated list of Kubernetes namespaces with key aggregated pod metrics: CPU usage and memory working set (summed across pods in the group), plus per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value in the window). Each namespace includes metadata attributes (k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.namespace.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / memory, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (namespaceCPU, namespaceMemory) return -1 as a sentinel when no data is available for that field.
// Plain Enter commits the edit; let Cmd/Ctrl+Enter pass through so a host
// form (e.g. a modal) can submit on it.
if(e.key==='Enter'&&!e.metaKey&&!e.ctrlKey){
e.preventDefault();
e.stopPropagation();
commitEdit();
}elseif(e.key==='Escape'){
// Contain Escape so it cancels the inline edit instead of bubbling up and
// closing the host drawer/modal.
e.preventDefault();
e.stopPropagation();
cancelEdit();
}
};
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.