The query builder hid both on a heatmap, on the grounds that the request
takes exactly one enabled query and a formula over a bucket axis could
only rescale it. Neither holds: the one enabled query the request takes
can be a formula, and the other queries it reads stay disabled.
Assisted-by: Claude Opus 5
The legend was hidden below two groups and every marker took the ramp
colour for where that group's densest cell fell on the colour bar. Both
read the legend as a ranking of counts, which it is not: colour already
means count on the grid, so a marker keyed to one states a second scale
that contradicts the first.
- Show the legend for a single group, and for an ungrouped query, which
`getLabelName` now names after the legend format or the query itself.
It was left unlabelled precisely because the legend never appeared.
- Give every marker the palette's extreme, matching the tooltip's own
marker. `resolveGroupPeaks` had no other caller.
Assisted-by: Claude Opus 5
The plugin's portal wrapper caps every tooltip at 600px and scrolls the
overflow, which took the cell identity and the footer with it once a
contribution list ran to dozens of groups. Capping the list keeps the
wrapper's own overflow from ever engaging.
Assisted-by: Claude Opus 5
A log axis packs boundaries close together, and the panel's precision rounds
them to the same text: 0.0625 and 0.0653 both print as 0.06 ms, so the axis,
the series labels and the tooltip all named two rows the same range.
Resolve one precision for the boundaries — the lowest at or above the panel's
that prints every pair distinctly, capped at four decimals — and hand it to
everything that labels a row. Full precision is left as it is, having nothing
above it to reach for.
Assisted-by: Claude Opus 5
A bucket row was keyed by its label, and a label is not unique: two adjacent
boundaries can round to the same text, so React saw one key twice and the
hover highlight settled on the wrong row.
Carry the row's index on the axis and key on that.
Assisted-by: Claude Opus 5
Switching into a heatmap swaps a histogram's percentile for sum, and picking
the metric there defaults to sum. Nothing undid it on the way out, so a panel
switched to time series sat on a value its own selector does not offer.
Mirror the transform: on a first visit to any other kind, a histogram holding
sum goes back to p90. Other metric types keep theirs, since sum is a real
choice for a sum or a gauge.
Assisted-by: Claude Opus 5
uPlot picks time ticks off a fixed increment ladder and anchors them at the
timezone's midnight, with no regard for how wide the heatmap's columns are,
so a grid line lands wherever it lands — usually through a cell rather than
between two.
The heatmap now supplies its own x splits: uPlot's increment rounded up to a
whole number of columns, stepping from the column edge nearest the
timezone's midnight. Round local times survive wherever a cell edge carries
one, and are given up only where none does. Month and year increments keep
the calendar walk, snapped per tick.
Assisted-by: Claude Opus 5
Picking a histogram metric hardcoded p90 as the spatial aggregation, which
left a heatmap showing a value its own selector does not offer. The options
already narrow to sum for the kind; the default has to follow.
Assisted-by: Claude Opus 5
The request takes exactly one enabled query and refuses a formula over the
cells, so the kind hides Add Query and Add Formula. Switching a panel to
Heatmap trims the query it carries over to match, since the builder renders
the first query alone and the leftovers would otherwise reach the request;
the per-kind cache restores them on the way back.
A histogram heatmap draws its axis from the `le` labels, so a percentile
draws the grid a count already draws and the statement builder sums either
way — offer sum alone, and swap a carried-over percentile for it.
Assisted-by: Claude Opus 5
The footer rendered Add New Query unconditionally, so a kind that hides
AdditionalQueries still offered it — and the queries it added went to the
query list beside the builder without ever reaching the editor, which
renders the first query alone when multi-query is off. Gate the button the
way Add Formula already is, drop the footer entirely when nothing is left
to add, and list only the queries the editor actually renders.
Assisted-by: Claude Opus 5
Brings in the backend half of the feature (#12764): the `heatmap` request type,
its bucket-axis resolution and validation in `querybuildertypesv5`, the querier
and metrics statement-builder support, the perses plugin, and the querier
integration tests.
This branch was carrying the generated contract (`docs/api/openapi.yml` and the
frontend schemas) copied from that branch while its Go sources lived elsewhere.
Both now regenerate to exactly what is committed, so the spec no longer runs
ahead of the code it describes.
The author's `heatmap-poc/` directory ("chore: push temporary poc for testing")
is left out — a standalone throwaway viewer, not part of the feature.
Assisted-by: Claude Opus 5
Brings in the declarative query-builder field config (#12793), which replaces
the hand-threaded field/signal narrowing this branch had grown.
- `queryBuilderFields` on a kind's definition is now a `QueryBuilderFieldsConfig`
(`{ [field]: { state } }`), so the Heatmap hides functions and having by
declaring them rather than by a per-signal `filterConfigs` rule
- `getSupportedDataSources` and `SIGNAL_TO_DATA_SOURCE` are gone: the dropdown
takes signals directly through `allowedDataSources`, which the editor fills
from the kind's `supportedSignals`, and maps them itself
- `isRawQueryKind` reads raw-ness off the declared request type, retiring the
`isListViewPanel` flag the builder was passing around
- `getQueryBuilderFields` and `isRawQueryKind` fold through this branch's
query/static fork, and `PanelEditorQueryBuilder` keeps taking the definition
it is handed rather than looking it up by kind
Assisted-by: Claude Opus 5
Every timestamp is a full column of cells, so at raw resolution a multi-day
range came back as tens of thousands of sub-pixel cells. The kind now declares
`bucketedStepInterval`, the same treatment Bar asks for, and a query without an
explicit interval gets the range-derived one (~80 columns). The helper and the
capability's doc drop the "bar" in their names: they describe kinds that draw
one mark per point, which a heatmap column is.
Assisted-by: Claude Opus 5
`InputNumber` renders a text input and keeps whatever is typed as its display
string, so letters showed in the field, emitted no bound, and silently reverted
to Auto on blur. The bounds now use the `Input type="number"` every other
numeric field in the config pane uses, parsed exactly as the axes' soft bounds
are: empty or unparseable leaves the bound derived.
Assisted-by: Claude Opus 5
The x axis prints a date only where the day turns over, so on a wide window a
bare time did not say which day the hovered cell was in — and a step interval
that spans days made the two ends read as the same one. Both ends now carry
`MM/DD`, and the module registers dayjs's utc/timezone plugins itself rather
than relying on another module having done it.
Assisted-by: Claude Opus 5
Both painted their text with the fixed vanilla ramp — dark-theme values, so on
a light surface the tooltip's title, count and hovered row were white on white
and the colour bar's labels and keys were washed out. They now read from the
popover/muted pair, which resolves to the same colours in dark mode. The "no
data" swatch takes the muted token too, so it matches the theme-aware hatch
`createHatchPattern` paints over a null cell.
Assisted-by: Claude Opus 5
The highlight is positioned from `valToPos`, which puts an end cell past the
axis when its time slice is only partly in view, so the border was drawn over
the axis and out into the panel. The overlay container spans exactly the plot
area, so it now clips its children — the DOM analogue of the canvas clip the
cells are already painted through.
Assisted-by: Claude Opus 5
The plot reports the hovered cell from inside its own render path, which React
can be driving — a resize or a data swap runs the plot's hooks during a commit,
and a rebuilt plot re-reports the cell the cursor is still sitting on. Writing
that straight to state nested an update inside the commit that caused it, and
the two fed each other until React aborted at its depth limit.
- the cell is coalesced onto the next frame and only written when it changed,
so a re-report costs nothing and the update leaves the commit chain
- the count joins row and column in a cell's identity, so a refetch under a
stationary cursor refreshes the value instead of keeping the old one
- `destroy` reports the cleared hover, which it used to drop silently, leaving
the colour bar's marker on a cell whose plot was gone
Assisted-by: Claude Opus 5
`useGetMetrics` rebuilds its array on every render, so the effect that copied
the resolved unit into state re-ran after every render of the panel editor —
the shape React reports as "Maximum update depth exceeded". The unit is a pure
function of the metrics' units, so it is now read straight off them: no state,
no effect, and no settling pass before the first render carries the unit.
Assisted-by: Claude Opus 5
Log took 1 as its bottom, which only fits whole counts: a grid of rates or
ratios sits entirely below it, so every cell collapsed onto the ramp's darkest
colour and the panel read as empty.
- the log floor now comes from the grid — its smallest positive value, capped at
six decades below the max so one tiny outlier cannot stretch the ramp over a
range nothing else occupies. Whole counts still floor at 1, as before
- a floor that already reaches the top of the domain (a single distinct value)
falls through to linear, so the max lands at the top of the ramp rather than
flattening the grid
Assisted-by: Claude Opus 5
queryBuilderFields on a panel definition now speaks the builder's own vocabulary
instead of a per-signal map keyed on IBuilderQuery fields, and the guard exposes
it as getQueryBuilderFields. The per-signal layer goes with it: its only use was
List hiding limit on traces, which the raw baseline now covers on both signals.
isRawQuery is read from the requestType a kind already declares rather than a
hardcoded ListPanel check, so raw-ness has no second place to drift from. That
also makes PanelEditorQueryBuilder's signal prop unused.
Assisted-by: Claude Opus 5
Formula's additional-filters block sat behind isAdditionalFilterEnable, and its
only call site passed a literal false, so none of it rendered. Removing it takes
the formula Having/Limit/OrderBy filters, AdditionalFiltersToggler and
FilterLabel with it, along with the query-filter maps that only fed the list.
QueryBuilderSearchV2 loses whereClauseConfig for the same reason: no caller ever
passed it, so the body-CONTAINS branch it guarded could not be reached.
Note that formula-level HAVING had no QBv2 equivalent, so this drops the only
implementation of it rather than one of two.
Assisted-by: Claude Opus 5
QueryAddOns, QueryAggregation, QueryFooter and QBEntityOptions now resolve what
they render from fieldsConfig rather than each inferring it. The old
filterConfigs prop is gone from the builder: nothing downstream ever read its
isHidden/isDisabled, and its whereClauseConfig entry had no consumer at all.
isListViewPanel becomes isRawQuery. The flag is named for a dashboard panel type
but lives in a component three explorers use, and what it really means is that
the builder edits raw rows. It now supplies the defaults for fieldsConfig and
allowedDataSources, which callers override per field.
allowedDataSources replaces the dropdown's own notion of list panels, and takes
signals rather than the legacy DataSource enum.
Assisted-by: Claude Opus 5
Introduces QueryBuilderField, a per-field hidden/disabled/pinned rule, and a
resolver. A config can only narrow what the builder already supports for the
current data source and panel type, so callers never restate the builder's own
rules. `reason` is required on `disabled` so an inert control always explains
itself.
Assisted-by: Claude Opus 5
Trims the commentary the heatmap work left behind, keeping the lines that carry
a constraint the code cannot: the server's step is authoritative, `null` is not
`0`, the heatmap request rejects functions and having, the step slider commits
on release. Comment-only; no behaviour changes.
The section opened on a list of fields — a dropdown naming ten palettes you
could not see, a number box for the step count — so the one thing those settings
produce, the ramp, was only visible on the panel behind the pane.
- a preview at the top resolves the ramp through the chart's own colour
resolver, so its bands are the grid's bands, down to how coarse the chosen
step count makes them; its ends carry the counts the ramp is stretched between
- the palettes are the swatches themselves, two across: "lava" and "ember" say
nothing about which is which
- the step count is a slider, committed on release so a drag doesn't write every
value it passes through into the panel spec
- opacity mode gets a Base colour row: four presets that hold up on both themes,
and a custom swatch for matching whatever else the dashboard uses
- the count bounds read like the Axes soft bounds, labelled Min / Max above an
Auto placeholder
- every control carries a line saying what it drives
Built from @signozhq/ui — Slider, InputNumber, Typography — plus the
ConfigSegmented toggles the pane already uses. The custom-fill trigger stays
antd's ColorPicker, the one control the design system has no counterpart for.
No Reverse toggle: `chartAppearance.colors` has no field for one, so it would
not survive a save.
#### Description
- Removes the legacy `/api/v1/checkout`, `/api/v1/billing`, and
`/api/v1/portal` routes and the licensing API layer that served them,
now that billing goes through the subscription endpoints.
#### Issues closed by this PR
ClosesSigNoz/platform-pod#3091
#### Description
- Billing is now authorized through the `subscription` permissions
instead of the admin role, so custom roles can be granted billing
access.
- Denied users keep the page, tab, and controls in view; usage shows the
denied callout and checkout/portal actions are disabled with a tooltip.
- Billing calls the `/api/v1/subscriptions` endpoints added in #12767
through the generated client.
#### Additional Information
Part of SigNoz/platform-pod#3091. Stacked on #12767; retarget to `main`
once that merges.
#### Description
- Auto refresh stopped in full screen: the timer lived inside the time
selector, which the full-screen branch unmounts along with the toolbar,
so the global time window never advanced and panels never refetched. The
timer now lives in a hook plus a headless `AutoRefreshTicker`, mounted
in the full-screen branch so exactly one timer runs in either mode.
- Table panel page size always snapped back to 10: the height-derived
page size was passed straight into antd's controlled
`pagination.pageSize` and `onChange` dropped the new size. A size picked
from the size changer now wins; panels that were never touched still
auto-fit to the panel height on resize.
Closes https://github.com/SigNoz/pulse-pod/issues/329
The editor's signal dropdown listed Logs, Metrics and Traces for every panel,
so a Heatmap — which only metrics carry a bucket axis for — invited a query it
would then refuse to render. List was already narrowed correctly, by an
`isListViewPanel` flag the dropdown reads, but that flag names the one panel it
was written for rather than what a panel can visualize, so a new kind with its
own signal set got everything.
- `getSupportedDataSources` joins the capabilities guard, mapping a kind's
declared `supportedSignals` onto the `DataSource` list the builder speaks
- `supportedDataSources` threads from the panel editor through QueryBuilderV2 →
QueryV2 → QBEntityOptions to the dropdown, which offers exactly those
- `isListViewPanel` stays as the fallback for the explorer call sites; an
explicit list wins over it, and List's declaration resolves to the same two
sources that flag was giving it, so its dropdown is unchanged
Assisted-by: Claude Opus 5
Registers `signoz/HeatmapPanel` on the V2 panel registry, wiring the heatmap
chart layer to the `heatmap` request type: the server returns one count per
bucket at each timestamp with the shared bucket bounds on the aggregation's
meta, and the panel renders that grid without ever re-binning it.
- the kind declares metrics only (the request rejects logs and traces), hides
`functions` and `having` (a count per bucket has nothing for either to act
on), and opts out of alerts and drill-down — a distribution per timestamp has
no single value to threshold or to drill into
- `axes.yScale` picks how the bucket axis distributes its row heights, offering
all four of auto, linear, log and symmetric log against the chart's scale of
the same name. Auto is seeded, so the control opens on the axis being drawn
rather than blank
- `chartAppearance.colors` gets its own editor — colour mode, palette (shown as
its ramp), colour scale, step count and the counts the ramp is stretched
between. Mode, palette and scale are seeded so the controls open on the ramp
the grid is drawn with
- Legend offers position only: the legend picks a group, and a group's swatch
colour is read off the same ramp as the grid rather than chosen
- drag-select is the one gesture the grid exposes, so the time axis still zooms
- `PANEL_TYPES.HEATMAP` joins the legacy enum for the V1 pivots the editor still
goes through (query conversion, the panel-type switch's query rebuild); the
V1 render maps get a null entry, since a heatmap only renders through V2
The generated API contract comes from the backend branch (`nv/heatmap`) rather
than from Go sources on this branch, so `docs/api/openapi.yml` is ahead of what
this branch could regenerate until that PR lands.
Assisted-by: Claude Opus 5
`HeatmapAxisScale` had two members, and `Log` quietly meant "log, or a
symmetric log if the boundaries make a plain log impossible". That conflated a
preference with a property of the data: a panel asking for `log` could not say
whether it wanted the compression or was merely accepting it, and a panel that
wanted a symmetric log had no way to ask.
- `Auto` is the new default and the only member that reads the layout: log where
the boundaries are all positive, symmetric log where they cross zero, linear
where they are all zero
- `Symlog` always draws the symmetric log, positive layouts included
- `Log` stays a plain log10. The boundaries a logarithm has no answer for — zero
and below — are pinned one bucket beneath the smallest positive one, so the
zero bucket every explicit-bounds histogram carries keeps its own row, tick and
label, and costs a bucket of height instead of the decade a symmetric log
spends on it. Several non-positive boundaries share that edge and squash
together, which is what `Symlog` is there for
- the adaptive geometry tests move to `Auto`, and the explicitly chosen scales
get a block showing where the three answers differ
Assisted-by: Claude Opus 5
The heatmap layer was written before the V1 cleanup moved the shared chart code
out of container/DashboardContainer/visualization into lib/visualization, so it
landed in the old tree and was the only thing left there.
- chart, utils, group-legend hook and tests move to lib/visualization/charts/Heatmap
- HeatmapChartProps joins the other chart props in charts/types.ts, alongside
TimeSeries/Bar/Histogram/Pie; the chart-local types file held nothing else
- the heatmap tooltip, its two lists, content helpers and styles move into
Tooltip/components/HeatmapTooltip, so the tooltip root keeps only the shell
and the flat per-chart tooltips
Also drops a dead `timezone` prop Heatmap passed to ChartWrapper, which does not
take one — a type error the unresolvable ChartWrapper import had been masking.
Assisted-by: Claude Opus 5
Bucket × time density grid drawn on canvas through uPlot hooks: columns are
time slices, rows are bucket ranges, and cell colour is the observation count,
so a distribution can be watched changing shape instead of collapsing to
percentile lines.
- renderer, palettes and DOM hover overlay under lib/uPlotV2/plugins/HeatmapPlugin
- bucket axis places log10 values on a linear uPlot scale, extending
symmetrically when the boundaries include zero or negatives
- `null` (no data) renders hatched and is never conflated with a `0` count
- purpose-built tooltip: neighbouring buckets, or per-group contribution when
the cell sums more than one group
- ColorBar scale key, reusable by other density visualisations
- group legend through the shared Legend, isolate on label / exclude on marker
The chart takes bucket bounds plus per-group series and pivots them itself.
Panel kind, spec and the `heatmap` request type land separately.
Assisted-by: Claude Opus 5
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
This PR adds one endpoint, `POST /api/v2/notification_channels`, whose
typed `{name, displayName, config:{kind, spec}}` body replaces v1's
pass-through Alertmanager receiver JSON. v1 routes are deliberately
untouched, so the diff is near-purely additive.
List/get/update/delete/test come in the next PR.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/296
<!--Anything reviewers should keep in mind while reviewing -->
Eventually references (rules, routing policies) migrate onto
internal_name, freeing name to become a user-editable display name. But
that will happen post rules migration so that all rules are on v2
<!--Please delete paragraphs that you did not use before submitting.-->
#### Description
- Adds the `builder_ai_query` envelope type, seeded by the AI explorer
via `initialQueryAIWithType`.
- AI queries pull filter-bar keys from
`/api/v1/ai_observability/fields/keys` and values from
`/api/v1/ai_observability/fields/values`.
- `QuerySection` drops the builder's custom order-by (`ExplorerOrderBy`
via `renderOrderBy`) for the list and trace panels, along with
`showTraceOperator`. Ordering for those views is the list's own concern,
not the builder's — the trace view supplies its own control in #12688.
`limit` and `having` are now rendered disabled on every panel instead of
hidden on list.
- Hides the span-scope selector for AI queries — "Root / Entrypoint
Spans" ANDs badly with the gate, since GenAI attributes sit on nested
spans. Derived from the query itself, so no new prop on the shared
builder.
- `createNewBuilderQuery` inherits `builderQueryType` from the first
query, the way it already inherits `source`.
- Response conversion reads aggregation metadata from both builder
envelope types, not just `builder_query`.
TDD -
https://app.notion.com/p/signoz/Query-Builder-Changes-3b4fcc6bcd1980889c7aed782665e78d
#### Issues closed by this PR
Closes -
https://github.com/orgs/SigNoz/projects/39/views/20?pane=issue&itemId=226748421&issue=SigNoz%7Cengineering-pod%7C5889
#### Screenshots / Screen Recordings
https://github.com/user-attachments/assets/985cb178-ffb0-405c-b0a6-82ec242c3d68
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
Both callers that stage a panel's query into the URL asked the capabilities guard
whether the kind was query-less before calling getPanelBuilderQuery — the same
question, phrased the same way, in two hooks that otherwise share nothing.
The function itself knows: it already reads the kind's definition for a default
signal, so it returns null when there is no query arm and callers skip the
compositeQuery param on a falsy result.
Assisted-by: Claude Opus 5
Forking the editor on authoring mode copied the whole frame into both arms: the
header, both ResizablePanelGroups, the handles and the layout persistence. The
two differ only in the pane sizes and in what fills the preview and editor
slots — everything else was duplicated, including the layout ids, so the arms
shared persisted state while each owned its own copy of the sizes. Changing one
would have silently diverged them.
PanelEditorLayout owns the frame and takes the slots. The per-mode sizes sit
together in PANE_SPLIT, where they can be compared, instead of inline in two
files: the query builder is a compact form so the preview keeps the room, while
a static kind's editor pane is the surface being worked in.
Assisted-by: Claude Opus 5
Phase 3b of the Text panel plan — the View modal gets the same shell the
editor got: ViewPanelModalContent now owns the draft and the kind-switch cache
(so a switch across authoring modes survives the branch swap) plus the
mount-only URL/handoff seeding, guarded so a composite query can only seed a
kind that takes one. The query body is today's content unchanged
(QueryViewModalBody); the static body renders the panel live over the kind's
editor pane, with a kind switcher and an "Edit panel" handoff that carries
in-modal edits — no time window, no query session, no drilldown.
useViewPanelMode drops its seeding and switch concerns and takes the hoisted
draft, which is what keeps its query machinery unmounted for static kinds.
Opening a static panel writes only the expanded-panel id: with no query to
stage or persist, nothing reaches the URL or the shared builder.
Assisted-by: Claude Fable 5
(cherry picked from commit 7af199fcb2)
Phase 3a of the Text panel plan. PanelEditorContainer becomes a shell owning
exactly the state that must survive a switch between authoring modes — the
draft and the kind-switch cache — and forks on the draft kind's `mode`:
- QueryEditorBody is today's editor body unchanged, now fed the hoisted draft
and the narrowed definition. The lower pane comes from the definition's
EditorPane: a shared QueryBuilderEditorPane for six kinds, and a List wrapper
that absorbs the columns-editor footer both the editor and the View modal
previously special-cased inline.
- StaticEditorBody is the query-less body: the kind's editor pane under a live
preview of the draft (the same StaticPanelBody the grid renders), saving with
`queries: []` — the only shape the API accepts. No builder seeding, no staged
run, no compositeQuery URL writes; opening the editor on a static kind stamps
no default query into the URL either.
- usePanelTypeSwitch handles static targets: first visit gets a fresh spec with
queries emptied and the query builder left untouched; the per-kind cache makes
the round trip restore both sides.
PanelEditorQueryBuilder now reads the narrowed definition it is handed instead
of looking capabilities up by kind, which also breaks the would-be import cycle
definition → pane → capabilities → registry → definition.
Still unreachable: no static kind is registered until the registration phase.
Assisted-by: Claude Fable 5
(cherry picked from commit f065874402)
Phase 2 of the Text panel plan. StaticPanel (grid) and StaticPublicPanel mount
a static kind's renderer behind the shared panel chrome — no fetch, no status
indicators, no time preference, no drilldown, because none of that exists
without a query. StaticPanelBody is shared by both hosts (and later the editor
preview) and resolves `dashboardId` from the edit-context store, so previews of
unsaved panels read variables the same way the grid does.
Still unreachable: no static kind is registered, so both arms are exercised by
fork tests with the registry mocked — asserting the query hook never mounts.
(cherry picked from commit 1f1fe69f30)
Phase 1 of the Text panel plan (frontend/docs/text-panel-implementation-plan.md).
A definition is now one of two shapes discriminated by a root `mode`: query
kinds declare their whole query surface (renderer, signals, query types,
builder fields, request capabilities); static kinds — none exist yet — declare
a renderer that takes no query data and an editor pane that replaces the query
builder. No dummy capabilities, no empty declarations standing in for "not
applicable".
Hosts fork on `mode` and pass the narrowed definition down: Panel and
PublicPanel become hookless forks over extracted QueryPanel/QueryPublicPanel
bodies, PanelBody takes the query arm's Renderer directly, and readers that
can't take the definition as a prop yet assert the arm via
requireQueryPanelDefinition. Leaf query hooks keep non-null contracts.
No behavior change: generated types are untouched, so the kind universe is
still the seven query kinds and the static arm of every fork is unreachable.
(cherry picked from commit 526ff46441)
useLegendSeries switched on the panel kind to pick how a panel's output becomes
legend entries — pie slices or flat series — while the kinds that expose the
colors control were already declaring it as Legend.controls.colors. Two places
described the same fact, so a new chart kind could declare the control and
silently get an empty color picker.
The colors control now carries the resolver instead of a boolean: declaring the
resolver is declaring the control, so the two cannot disagree. The hook becomes a
lookup and a call with no switch, and LegendSection's truthiness check is
unchanged.
legendSeries.ts moves from PanelEditor/utils to Panels/utils — it only ever
imported from Panels and queryV5, and kinds cannot import up into the editor. Both
resolvers take one args object so pie needs no placeholder parameter.
Assisted-by: Claude Opus 5
Both alert helpers switched on the panel kind: readPanelUnit listed the four
kinds that carry a unit, readPanelThresholds listed the shapes each kind's
thresholds take. Every new kind has to be added to both, and a missed one loses
its alert prefill silently — there is no failure, just a missing unit.
Both facts are already declared. A kind's Formatting section says whether it
exposes a unit, and its Thresholds section says which variant it edits, so
getSectionControls answers both by reading the kind's own sections.ts. It also
replaces the private copy of the same lookup in newPanelSeed.
A table variant contributes no prefill: its thresholds are per column, which has
no meaning for a panel-wide alert condition.
Assisted-by: Claude Opus 5
The new-panel picker and the editor's kind switcher both read a hand-maintained
PANEL_TYPES array. A kind absent from it renders fine on a saved dashboard but
can never be created or switched to, and nothing catches the omission.
Each kind now declares its picker icon next to the displayName it already
declares, and both surfaces render Object.values(PANELS). Registry declaration
order is display order, so registry.ts is reordered to keep today's tile order.
The parallel array and its PanelType interface are deleted.
Assisted-by: Claude Opus 5
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
If an API request body has a field that cannot be empty, but backend can
fill in a default value for it, then backend should fill that default
value only when the field is omitted in the request. If the user has
explicitly sent `""`, then backend should reject it so that there is no
request-response drift.
Such fields can be typed as the new `UnsetOrNonEmptyString` which has
custom unmarshalling logic. If the field is set in the request json,
then the custom unmarshal logic is called and explicit `""` is rejected.
If the field is not set, then the function is not called, and further
backend logic is free to fill in the default value.
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
Came out of notification channels V2 API work.
<!--Please delete paragraphs that you did not use before submitting.-->
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
This is so that a round trip drift doesn't happen. This was caught for
incident io in its PR but we missed it out here.
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
Found as a part of round trip testing in v2 notification channels create
API
<!--Please delete paragraphs that you did not use before submitting.-->
#### Description
Unchecking a value in a Quick Filter V2 checkbox filter could not be
undone: the first click excluded the value (`not in`), the second
flipped it to `in` instead of clearing it, and the third appeared to do
nothing.
Quick filters dispatch through the URL, and the composite-query parser
merges `filters.items` into `filter.expression` on the way back in. That
merge only adds and rewrites clauses — it never drops one — so a clause
left behind in the expression resurrects a filter the user just removed.
This is why the bug shows up only for `filter.expression` and not for
`filter.items`.
Two causes, both in `applyCheckboxToggle`:
- Several branches removed the key from `filters.items` without removing
it from `filter.expression`. Rather than patching each removal site, the
expression is now re-derived from the updated items once before
returning. That also covers operator swaps (`not in` → `in`), which the
merge cannot rewrite in place because it keys on key + operator. Four
ad-hoc strips became redundant and were dropped.
- Under a `NOT IN` clause, the "user clicked an unchecked value, so
select it" branch fired for every unchecked value. But under `NOT IN`
the unchecked values are exactly the excluded ones, so that branch
swallowed the "re-include this value" case and the removal branch below
it was unreachable. It is now gated on the value not already being
excluded; a genuinely unselected value in **All values** still becomes
`in`.
Toggling a value is now a two-state cycle: no clause ⇄ `not in
['value']`.
The second commit narrows what that re-derivation is allowed to touch,
fixing two adjacent defects of the same kind:
- It stripped **every** clause for the attribute key, including
predicates the checkbox does not own (`CONTAINS`, `EXISTS`, a range).
Clearing a filter deleted such a clause outright — the header's Clear
button stays active even when the checkboxes are disabled by a second
clause on the key — and a plain toggle rewrote it, losing the spelling
the user typed. `removeKeysFromExpression` takes an optional operator
restriction, and the checkbox passes the four operators it actually
emits.
- It matched keys literally while the items side matches by base name
via `isKeyMatch`, so a context-prefixed clause such as
`resource.service.name` was left in the expression and resurrected the
filter. Both sides now agree on which spellings are the same filter.
- `clearFilterFromQuery` stripped the expression at every query index
while filtering items only at the active one, churning a clause in other
queries that the round trip put straight back. It now leaves non-active
queries alone.
#### Issues closed by this PR
Closes https://github.com/SigNoz/platform-pod/issues/3054
#### Additional Information
The 127 pre-existing Quick Filters tests pass both before and after this
change — they assert UI state and the dispatched filter items, never the
resulting expression, which is the gap that let this through.
`checkboxFilterQuery.test.ts` replaces that gap with a table of 40 cases
that assert the structured items and the shipped expression
**together**, each one driven through the real URL round-trip. Against
the code before this PR the same table fails 9 cases. It covers the
incident's own click sequence — re-checking a value that is actually in
the exclusion list — which nothing previously exercised.
One unrelated pre-existing failure in this area, for anyone running the
suite: `QuerySearch.test.tsx › fetches key suggestions on mount for
LOGS` fails on `main` when that spec runs on its own.
#### Description
- Adds a `subscription` domain: `POST`, `PUT`, and `GET
/api/v1/subscriptions`, wired with `CheckResources` + `ResourceDef`s on
the `subscription` metaresource (`create`, `list` + `update`, `read`).
Community gets a noop implementation; enterprise talks to Zeus.
- Migration `125_add_subscription_tuples` backfills the admin
subscription tuples for existing organizations.
- The legacy `/api/v1/checkout`, `/api/v1/billing`, and `/api/v1/portal`
routes are untouched; they are deleted once the frontend has moved.
#### Additional Information
Part of SigNoz/platform-pod#3091.
#### Description
V2 panels answered "how does this panel's query behave?" by comparing
against the legacy `PANEL_TYPES` enum. Each kind now declares it, so
adding a kind means stating its behaviour once instead of finding every
switch that should have mentioned it.
- **Kinds declare their query behaviour** — request type, table
formatting, step-interval and order treatment, paging, list-view
authoring, trace operator. `buildQueryRangeRequest` takes that block, so
the `PANEL_TYPES.BAR` / `.LIST` / `.TABLE` branches are gone. An
exhaustive `Record<PanelKind, …>` test means a new kind can't ship
without declaring its request shape.
- **The capabilities are passed in, not looked up.** The panel registry
carries every renderer with it, so importing it into the data path drags
the app's API client into anything that touches the request builder. The
call sites already resolve the definition.
- **The chart layer no longer infers a time axis from a panel type.**
`UPlotAxisBuilder` decided X-axis date formatting from a hardcoded
`[TIME_SERIES, BAR]` list, so a chart that plots time but isn't one of
those two silently lost its formatted ticks — no type error, no failing
test. Callers now declare `isTimeAxis`.
- **`getPanelDefinition` always resolves.** It was typed to return a
definition for any `PanelKind`, but the registry only holds registered
kinds, and a spec from a newer SigNoz names one this build has never
heard of. Callers coped by truthiness-checking a value the type said
couldn't be falsy — a lint autofix had already deleted one such guard in
`PublicPanel`. Unknown kinds now resolve to `UNSUPPORTED_PANEL`, which
declares nothing and renders as unsupported; `isPanelKindSupported` is
the separate question the lazy fetch and editor session actually needed.
- **Analytics gained `panelKind`** on all seven panel events, alongside
the existing `panelType` so current reports keep resolving. `panelType`
can't distinguish two kinds that map onto it.
- **Removed `ViewPanelQueryBuilder`** — no importers; the View modal
renders `PanelEditorQueryBuilder`. It referenced a stylesheet class that
no longer exists.
Behaviour is unchanged for every registered kind. The one visible
difference: a panel whose kind this build can't render now says so,
instead of rendering a header above an empty body.
#### Issues Closed
Closes https://github.com/SigNoz/pulse-pod/issues/279
#### Additional Information
- **Read it commit by commit** — each is one theme (declare / request
path / axis / builder mode / analytics / registry), and the diff is
mostly deletions once the declarations are in place.
- The legacy enum still appears in ~28 V2 files, all of it *translation
at a boundary* rather than a decision: the V1 `Query` pivot
(`mapCompositeQueryFromQuery` writes `panelType` into
`ICompositeMetricQuery`), URL params (`graphType` / `panelTypes` are a
serialised contract), the shared `QueryBuilderV2` provider (where
`panelType` is provider state read by its subcomponents), and analytics.
A follow-up will quarantine those into a single boundary module with a
lint rule keeping them there.
- The last commit deletes `resolveQueryCapabilities`, added earlier in
this branch: it existed only to absorb a missing definition, which the
registry no longer produces.
The V1 variable engine had no writers left: nothing wrote selectedValue,
so getDashboardVariables produced undefined values, variableFetchStore
was never updated, and the dependency graph and derived store fields
only fed that store. The shared store's one remaining job is publishing
the open dashboard's dynamic variables for query-builder autocomplete,
which needs a name and an attribute.
Replace it with a suggestion feed and delete the rest, including the
panel variables prop that no GridCard caller passed and
useResolveQuery's dashboardData option that no caller supplied.
useGetResolvedText loses its only variable source and becomes the title
truncation its callers already used it for.
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/326
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
<!--Please delete paragraphs that you did not use before submitting.-->
> **Stacked PR — merge bottom-up.** This is part of a stack retiring the
V1 dashboard frontend.
>
> | | PR | Change |
> |---|---|---|
> | 1 | #12647 | delete dead V1 dashboard code |
> | 2 | #12648 | retire the V1 panel editor and its widget route |
> | 3 | #12649 | legacy notice for unmigrated public dashboards |
> | 4 | #12650 | retire the V1 dashboard store |
> | 5 | #12651 | move the shared chart layer to `lib/visualization` |
> | 6 | #12652 | consolidate the widget-card stack under
`container/WidgetCard` |
> | 7 | #12653 | split `types/api/dashboard/getAll` |
> | 8 | #12654 | drop the `V2` suffix |
>
> `CODEOWNERS` for all of the above is split out into **#12707**, which
is *not* part of this stack (based on `main`) and should merge after it.
>
> Review this one against **#12647**, not `main`.
---
#### Description
`ROUTES.DASHBOARD_WIDGET` (`/dashboard/:dashboardId/:widgetId`) was
still registered but no UI linked to it, and its page fetched `GET
/api/v1/dashboards/{id}` — which the backend answers **501**. V2 serves
panel editing at `/dashboard/:dashboardId/panel/:panelId`.
- Extract the 12 modules other features still need out of
`container/NewWidget` first, then delete the route, the page and the
container.
- Threshold/format/time types → `types/api/widgets/threshold`,
`constants/formats/*`, `constants/timePreference` (fixing the
`alertFomatCategories` spelling on the way).
- `QueryTypeTag`, `PlotTag`, `populateMultipleResults`, the ContextLinks
utils and the four externally-used `utils` exports → `components/`,
`lib/query/`, `utils/contextLinks/`.
- The two raw query editors →
`container/QueryBuilder/rawQueryEditors/{PromQL,ClickHouse}`, where the
rest of the query-builder UI lives.
- Delete the V1 write path (`useUpdateDashboard` →
`api/v1/dashboards/id/update`) and the orphaned bootstrap chain.
#### Additional Information
**Two behaviour changes worth a look:**
1. **Meter Explorer's "Add to dashboard" was already broken.** It built
a V1 editor URL (`/dashboard/:id/new`), so users landed on the 501 page.
It now uses `useGetExportToDashboardLink` like the Logs, Traces and
Metrics explorers. This is a fix, but it touches Meter Explorer.
2. `FullView`'s **Switch to Edit Mode** button is removed — it built its
link with `generateExportToDashboardLink`, which goes away here, and it
was gated on V1 state nothing populates, so it never rendered.
`WidgetHeader`'s **Edit**/**Delete**/**Clone** items are left alone. No
caller lists them in `headerMenuList`, so nothing renders them either
way, and leaving them keeps this PR scoped to the editor route.
Two tests changed assertions rather than just mocks
(`WidgetGraphComponent.test.tsx`, `ExplorerOptionWrapper.test.tsx`) —
those are the diffs to read closely; the rest is mechanical.
Also extracts `ColumnUnit` to break the `getAll` ↔ `threshold` import
cycle the type move would otherwise have created.
**Verification:** `tsgo`, `lint`, `jest` (751 suites / 7386 tests),
`build`, `knip` all clean.
#### Description
- Removes the `get_meters_from_zeus` feature flag; `GET /api/v1/billing`
now always fetches usage from Zeus.
#### Additional Information
Part of SigNoz/platform-pod#3091. First of four PRs; the new FGA-gated
zeus subscription endpoints follow in the next one.
#### Description
- Adds a **Won't-fix resolution** input to the Jira channel form
(Advanced section), with a help line explaining what it does. The
backend already supported `wont_fix_resolution`; it was API-only until
now.
- When set (e.g. `Won't Do`), a Jira issue resolved with that resolution
is not reopened when the alert fires again; a new issue is created
instead. Optional; empty keeps the current behavior.
- Editing a channel now preserves an API-set `wont_fix_resolution` on
re-save (it was silently dropped before).
#### Screenshots / Screen Recordings
<img width="1441" height="110" alt="Screenshot 2026-09-03 at 4 10 12 PM"
src="https://github.com/user-attachments/assets/fcefa36d-1f41-4bac-acf9-334356e52267"
/>
> **Stacked PR — merge bottom-up.** This is part of a stack retiring the
V1 dashboard frontend.
>
> | | PR | Change |
> |---|---|---|
> | 1 | #12647 | delete dead V1 dashboard code |
> | 2 | #12648 | retire the V1 panel editor and its widget route |
> | 3 | #12649 | legacy notice for unmigrated public dashboards |
> | 4 | #12650 | retire the V1 dashboard store |
> | 5 | #12651 | move the shared chart layer to `lib/visualization` |
> | 6 | #12652 | consolidate the widget-card stack under
`container/WidgetCard` |
> | 7 | #12653 | split `types/api/dashboard/getAll` |
> | 8 | #12654 | drop the `V2` suffix |
>
> `CODEOWNERS` for all of the above is split out into **#12707**, which
is *not* part of this stack (based on `main`) and should merge after it.
---
#### Description
V2 dashboards serve `/dashboard` and `/dashboard/:id` unconditionally —
there is no feature flag — so the V1 page bodies and everything
reachable only from them are dead.
- Point the routes straight at the V2 pages and delete the V1 shims.
- Delete `container/ListOfDashboard`, the non-`visualization` half of
`container/DashboardContainer`, and the `GridCardLayout` grid shell.
These are mutually entangled (`ListOfDashboard` ↔ `DashboardDescription`
is an import cycle), so they have to go together.
- Rescue the shared code that lived inside them before deleting: the
variable dependency graph → `lib/dashboardVariables/dependencyGraph`,
`uniqueOptions` → `NewSelect`, panel-type items →
`GridCardLayout/panelTypeItems`.
- Drop two menu affordances that no live caller could render: panel
**Delete**/**Clone** (no consumer lists them in `headerMenuList`) and
the **Dashboard Variables** drilldown submenu (route-gated to
`/dashboard/:id`, which V2 serves through its own drilldown).
- V2's variable cycle detection now uses V2's own dependency helpers
instead of casting its form model into V1 types.
#### Additional Information
`container/DashboardContainer/visualization` is deliberately untouched —
it is the shared chart layer, not V1 code. It moves later in the stack.
**Verification:** `tsgo --noEmit`, `lint`, `jest` (766 suites / 7491
tests), `build`, and `knip` (unused files 51 → 41, zero newly orphaned)
all clean.
#### Description
- Moves all ingestion key and limit routes from the legacy `EditAccess`
gate to `CheckResources` + `ResourceDef`s (kinds `ingestion-key` /
`ingestion-limit`) with scoped security schemes.
- Limit create checks `create` on the limit plus `attach` on the parent
key; limit delete checks `delete` plus `detach`, resolving the parent
key via the new upstream get-limit call.
- Grants `attach`/`detach` on `ingestion-key` to the admin and editor
managed roles; migration 120 backfills all ingestion tuples for existing
organizations and refreshes the managed roles' `transaction_groups`.
Stacked on #12625.
#### Issues closed by this PR
ClosesSigNoz/platform-pod#2651
#### Additional Information
- Wiremock fixtures now use UUID key/limit IDs — the metaresource FGA
selector only accepts UUIDs.
- Delete requests make one extra upstream GET (parent-key resolution)
before the authz verdict, mirroring the serviceaccount extractor
pattern.
#### Description
The old API didn't support telemetryFieldKey, so adding a new v2 API to
support it.
This PR
* Migrates old data to the new one.
* Existing API's now internally stores it in the new struct so that they
don't break the UI.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5947
## Additional details
* the old api is safe with new field as it is just a subset of it.
#### Description
Adds support for related values in ai observability field values.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5975
## Pull Request
---
### 📄 Summary
* Span list (raw) queries can now leverage trace-level `trace.` filter
conditions leading to the trace-level component being qualified with
`__trace_scope`.
* An error is now raised if the span list is ordered by the trace level
key.
The following constraint is known: in case of ordering by `timestamp`,
the querier processes the span list by time bucket thus performing
trace-level aggregates calculation per bucket not within a time window.
The records are kept separately.
#### Issues closed by this PR
Fixes https://github.com/SigNoz/engineering-pod/issues/5976
---
### ✅ Change Type
_Select all that apply_
- [✅ ] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated: ✅
- Manual verification: ✅
- Edge cases covered:
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: None
- Potential regressions:
- Rollback plan:
#### Description
- Adds first-class ingestion limit APIs under
`/api/v2/gateway/ingestion_limits`: create (`keyId` in body), get,
update, and delete by `{limitId}`. Get proxies the new upstream `GET
/v1/workspaces/me/limits/{limitID}`.
- Adds key read APIs: `GET /api/v2/gateway/ingestion_keys/{keyId}` (key
by id — upstream does not embed limits here) and `GET
/api/v2/gateway/ingestion_keys/{keyId}/limits` (limits for a key, with
current-period usage metrics).
- Marks the existing limit routes (`POST
/ingestion_keys/{keyId}/limits`, `PATCH/DELETE
/ingestion_keys/limits/{limitId}`) as deprecated; they keep working
unchanged.
- Renames the old create body to `DeprecatedPostableIngestionKeyLimit`;
`PostableIngestionKeyLimit` is now the first-class body carrying
`keyId`. Handlers decode via `binding.JSON` and the create response is
`types.Identifiable`.
Part of SigNoz/platform-pod#2651.
#### Additional Information
- OpenAPI spec and the generated frontend client are regenerated; the UI
stays on the deprecated routes for now.
- Requires the upstream get-by-id endpoints from
SigNoz/opentelemetry-gateway#96 (merged and deployed).
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
Convert the existing `name` to store an immutable DNS1123 internal name
of a notification_channel, and add a display name column where the data
from the existing `name` column will go.
This is just a database level change. The internal name is not being
used by any consumer, be it the API or rules or route policies. All that
will come in subsequent PRs
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/pulse-pod/issues/296
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
Eventually references (rules, routing policies) migrate onto the
internal name, freeing the display name to become a user-editable. But
that will happen post rules migration so that all rules are on v2.
<!--Please delete paragraphs that you did not use before submitting.-->
## Description
Frontend for the **incident.io** alert channel (backend in #12644 — this
PR is stacked on it).
- Adds **incident.io** to the channel-type dropdown with a settings
form: alert source **URL** + **token** (both required), and
**title/description** template fields prefilled with the backend
defaults — same UX as Jira/JSM.
- A tip above the form links to the setup docs (create an HTTP alert
source in incident.io, copy URL + token).
- Client-side validation mirrors the backend for a nicer error
experience: both fields required, URL must be an alert events URL
(`…/v2/alert_events/http/<source_config_id>`).
- `send_resolved` is seeded **on** so incident.io alerts resolve with
the rule (backend can't default it — same reasoning as JSM).
- **Additional metadata** section: key-value rows merged into every
alert's metadata on top of the alert's labels (channel wins on clash);
values may use templates.
- Create, edit and test-channel flows all wired; editing prefills from
the stored `incidentio_configs`.
Notes for the reviewer:
- Follows the JSM Ops form/handler pattern file-for-file; no new
patterns introduced.
- Tests: 4 create-flow cases (fields render, required-field error, URL
validation error, payload shape with defaults) + 1 edit-flow payload
case.
## Issues closed by this PR
ClosesSigNoz/pulse-pod#173
---------
Co-authored-by: Naman Verma <naman.verma@signoz.io>
## Description
Adds **incident.io** as a native alert notification channel, using
incident.io's HTTP alert source (Alert Events V2 API)
- A channel is configured with the alert source's **URL + token**; title
and description templates are prefilled with the same defaults as
Jira/JSM.
- Alerts fire and auto-resolve in incident.io; the description is
markdown (incident.io renders it natively) and carries the usual deep
links — **View in SigNoz, related logs, related traces**.
- All rule labels (severity, team, custom labels) are sent as
**metadata**, so users can map them to incident.io attributes and
route/escalate on them.
Notes and decisions for the reviewer (full details in the [discussion
ticket and doc](https://github.com/SigNoz/pulse-pod/issues/171)):
- **Dedup:** one incident.io alert per notification group, keyed by the
group key hash (same identity Jira uses). A resolve targets the same
key; re-fires after resolve correctly open a fresh alert — no key
rotation needed.
- **Repeat notifications are no-ops on incident.io** (it drops duplicate
firing events) — unlike Jira, we cannot append updated values to an open
alert; operators click through to SigNoz for current values.
- **Limits:** description capped client-side under incident.io's
documented 512 KB payload limit; retries only on 429/5xx (documented
limit: 120 events/min per source).
- **Channel-level metadata:** optional key-value pairs on the channel
config, merged into every event's metadata on top of the alert's labels
(channel wins on key clash — Opsgenie precedent). Values are
template-expanded; a value that fails to expand is sent raw with a
warning logged, so delivery never breaks on a bad template.
- Upstream alertmanager ships its own basic incident.io notifier — our
config **shadows it** so the SigNoz notifier (templates, dedup,
metadata) handles delivery.
- Frontend (channel form) follows in a stacked PR.
## Issues closed by this PR
ClosesSigNoz/pulse-pod#172
---------
Co-authored-by: Naman Verma <naman.verma@signoz.io>
#### Description
- Adds strongly typed `/api/v4/licenses` endpoints on the apiserver with
OpenAPI definitions: activate, list, get, refresh, delete, and `GET
/api/v4/licenses/active`.
- Wires resource authz (`license:create/list/read/update/delete`) via
`CheckResources`; `GET /active` is `OpenAccess` and never includes the
license key — the key is returned only by the FGA-gated get-by-id, so
orgs can grant `license:read` selectively. Migration 118 backfills
license tuples for existing orgs.
- Delete is allowed only for non-cloud licenses; licenses managed by
SigNoz Cloud are rejected.
- v4 responses are camelCase with lowercase enum values; v3 routes and
stored license data are unchanged.
#### Issues closed by this PR
Closes https://github.com/SigNoz/platform-pod/issues/3076
#### Description
- Fixes the issue where applying filter for value / filter out (also
group by and replace filter) from the log details drawer was removing
the added columns from the table.
- As part of this fix, we rename `id` and `name` fields in
explorerTabChange input type. Now it reads `viewName` and `viewKey` and
avoids confusion
- Now consumers of explorerTabChange need to send viewName and viewKey
only if needed. As now this is an optional field
#### Screenshots/Recording
Before
https://github.com/user-attachments/assets/4c6145e3-4979-4386-9230-f74eb00721c8
After
https://github.com/user-attachments/assets/52bc3083-40a2-4345-aebd-63770882b06c
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/6012
#### Additional Information
- same issue was present in the old drawer (group by, replace filter)
and the metrics explorer detail (passed the metric name)..both fixed
here. metrics never surfaced as a bug since it opens in time series with
no columns to collapse
- filter for/out in the old drawer was never affected, it uses a
different add to query path
#### Description
Adds **Jira** and **JSM Ops** as alert channel types in the existing
channel flow. No new pages or endpoints — both reuse the same create /
edit / list / test actions as every other channel.
**Jira**
- Required fields: Jira Cloud site URL, Atlassian email + API token,
project, and issue type. **Summary** and **Description** are prefilled,
editable templates (the rich issue body is built server-side).
- The form recommends using an Atlassian **service account**, with a
link to the docs.
- Advanced Options: priority, labels (chip input), resolve/reopen
transition-name overrides, and the reopen window.
- Client-side validation mirrors the backend (must be an
`https://….atlassian.net` URL; reopen window ≥ 1m) purely for a
friendlier error — the backend enforces the same rules.
**JSM Ops**
- The JSM **integration API key** is the only required field — there is
no site or region to configure.
- **Message**, **Description**, and **Priority** are prefilled, editable
templates; **Tags** is a chip input (defaults to `signoz`).
**Shared behaviour**
- `Send resolved alerts` is on by default; on resolve, Jira transitions
+ comments the issue and JSM Ops closes the alert.
- Optional fields left empty are omitted from the payload, so the
backend applies its own defaults.
- Jest tests cover both forms: rendering, prefilled defaults, validation
errors, and the exact save payloads (`jira_configs` / `jsmops_configs`).
- Generated API client types are regenerated to include
`jsmops_configs`; locale strings added for all new fields.
#### Issues closed by this PR
Stacked on top of #12478 · Discussion: SigNoz/pulse-pod#169 · Closes
SigNoz/pulse-pod#170
#### Screenshots / Screen Recordings
Jira Form :
<img width="1512" height="823" alt="Screenshot 2026-08-18 at 1 16 49 PM"
src="https://github.com/user-attachments/assets/1a3a3419-de1b-4bf1-8a71-50d8341e5d47"
/>
Expanded Jira advanced options :
<img width="1444" height="406" alt="Screenshot 2026-08-18 at 1 17 08 PM"
src="https://github.com/user-attachments/assets/d1298009-b0f3-4f3d-b960-b805b12f5026"
/>
JSM Ops Form:
<img width="1479" height="631" alt="Screenshot 2026-08-18 at 1 19 11 PM"
src="https://github.com/user-attachments/assets/678b130b-8b1f-41c8-a78a-b6bd58ae2904"
/>
JSM Ops Advanced Options:
<img width="1455" height="239" alt="Screenshot 2026-08-18 at 1 19 22 PM"
src="https://github.com/user-attachments/assets/a4425b39-b7e1-4f10-bd0a-83dbc678693a"
/>
#### Additional Information
Notes for reviewers:
- **This branch is stacked on #12478**, so the diff shows the backend
commits too — only the `frontend/` files are new here.
- Follows the pattern of the Google Chat channel frontend.
- **JSM Ops seeds `send_resolved: true` in its prefilled config on
purpose** — the backend cannot default it to on, so the UI carries the
default and sends it explicitly.
- **Tags is a chip input in the UI, but the backend takes a
comma-separated string** — joined on save, split back into chips on
edit-prefill.
- Jira's reopen window is sent as a duration string (`"72h"`) even
though the generated DTO types it as a number, so it's cast at that one
boundary. `custom_fields` stays API-only and is not surfaced in the
form.
---------
Co-authored-by: Naman Verma <naman.verma@signoz.io>
#### Description
Adds the **Containers** section to the Kubernetes tab of Infrastructure
Monitoring, breaking each pod down into the app and sidecar containers
running inside it — which the Pods view rolls up into a single row — so
you can tell which container in a pod is throttling, leaking memory or
crash-looping.
- Backed by `POST /api/v2/infra_monitoring/kube_containers` through the
generated client. Filtering, grouping, time range, pagination, column
customization and the instrumentation checks callout all come from the
shared k8s entity framework, so this is mostly configuration rather than
new machinery.
- **List columns:** container name, pod, image:tag, kubectl-style
status, readiness, restarts, CPU and memory usage plus request/limit
utilization. Namespace, node, cluster and deployment sit behind the
column selector. Grouped rows show per-status and per-readiness counts.
- **Detail drawer:** ten `/v5/query_range` charts scoped to the single
container, plus the logs, traces and events tabs. Events are scoped to
the container's *pod*, since Kubernetes emits events per pod rather than
per container.
- A container's identity is the `(k8s.pod.uid, k8s.container.name)` pair
— a container name alone repeats across replicas, and a container ID
changes on every restart. Every other k8s entity is addressable by a
single name, so the first commit widens `SelectedItemParams` with an
optional container name, alongside the cluster and namespace slots that
already serve that purpose.
Columns and charts follow the descriptions in
https://github.com/SigNoz/signoz.io/pull/3644.
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5547
#### Additional Information
- Reviewed best commit by commit: identity foundation, then shared
constants/helpers, then the entity itself. Each stands on its own.
#### Description
This PR makes sure that the old migrations of quick filters are
decoupled from types as they should and not imported.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
Part of https://github.com/SigNoz/engineering-pod/issues/5947
#### Description
Adds two Atlassian alert channels. Backend only — frontend is #12488;
channels are created via the API.
**Jira issues — `jira_configs`**
- A firing alert creates a Jira Cloud issue; when the alert resolves,
the issue is transitioned to done. A re-fire within 3 days reopens the
same issue instead of creating a new one. 3 days is default but can be
edited via frontend form.
- The issue body is rich **Atlassian Document Format (ADF)**: a status
panel, the rendered alert description, and deep-links back to SigNoz.
- Re-fires keep the issue in sync (summary and description are
refreshed), and every notification after the first — re-fire, resolve,
reopen — also posts a **comment** carrying the same rich ADF snapshot,
so the issue holds a full lifecycle timeline.
- Per-rule custom notification templates (title/body) are honored, same
as every other channel; multi-alert custom bodies render as
divider-separated sections.
- Auth is Atlassian email + API token; Atlassian **service accounts**
also work (routed via the `api.atlassian.com` gateway automatically —
the cloud id is resolved server-side and client-supplied values are
ignored). Jira Cloud only.
**JSM Ops alerts — `jsmops_configs`**
- A firing alert opens a JSM Operations alert (the ex-Opsgenie alert
product); resolve **closes** it. A fire after close opens a fresh alert
— there is no reopen window.
- Re-fires dedupe into the same alert and increment its count. The alert
description keeps the first-fire snapshot; the value-over-time story
lives in the notes.
- Every fire and the resolve appends a **note** to the alert. JSM Ops
notes support **plain text only** (they render neither HTML nor
markdown), so notes use a new plain-text renderer with links flattened
to `text (url)`.
- The alert description supports JSM's **HTML subset**, rendered from
the same markdown templates.
- Auth is the JSM integration API key. No region/site config needed.
**Also in this PR**
- Unit tests for both config types, both notifiers, and the new ADF +
plain-text renderers.
- OpenAPI spec regenerated (adds `jsmops_configs`).
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#168 · Discussion: SigNoz/pulse-pod#169
#### Screenshots / Screen Recordings
Jira alert issue:
<img width="1171" height="739" alt="Screenshot 2026-08-18 at 12 49
31 PM"
src="https://github.com/user-attachments/assets/1ec74757-5d4d-4534-a5ca-13bed07cce72"
/>
Jira issue comments as a timeline:
<img width="1034" height="746" alt="Screenshot 2026-08-18 at 12 50
32 PM"
src="https://github.com/user-attachments/assets/09afb687-b62b-4eb3-86ab-39489e38513e"
/>
JSM Ops alerts page look:
<img width="1317" height="460" alt="Screenshot 2026-08-18 at 12 52
29 PM"
src="https://github.com/user-attachments/assets/dd5a60b5-ea6c-49b7-afde-f3b2c72b178a"
/>
JSM Ops alerts main body + comment timeline ( comments only support
plain text today ) :
<img width="1323" height="784" alt="Screenshot 2026-08-18 at 12 53
10 PM"
src="https://github.com/user-attachments/assets/871fb91c-307a-4c67-b2c4-bc9548506cbc"
/>
#### Additional Information
Notes for reviewers:
- Jira shadows upstream Alertmanager's `jira_configs` so our notifier
handles it instead of upstream's; this needs a small dedupe in
`PostableChannel.JSONSchema()` and leaves every other channel type
untouched.
- JSM Ops reuses the existing Opsgenie notifier; all new behaviour sits
behind a single `advancedFeatures` flag, so plain Opsgenie is unchanged
when it's off.
- `send_resolved` defaults off for both channels, so resolve-time
behaviour (Jira transition, JSM close + resolved note) needs it set on
the channel; the frontend will send it on by default.
- Notes are best-effort: a permanently-failed note (e.g. the first-fire
note racing JSM's asynchronous alert create) is dropped with a warning
instead of failing the whole notification. Nothing is lost — that first
datapoint is already in the alert body; retryable failures (429) still
retry.
---------
Co-authored-by: Naman Verma <naman.verma@signoz.io>
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
Patch and Edit Rule APIs would call syncRuleStateWithTask instead of
adding the task blindly, which is what the create API was doing. This PR
fixes the incorrect call in the create API
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/312
#### Description
Adding support for system dashboards.
* as of now updates are only through new versions in the file.
* user cannot update the dashboard
* For now kept the dashboard content empty and will raise it separately.
Closes https://github.com/SigNoz/engineering-pod/issues/4501
#### Description
**What was broken:** on a stack using the new JSON log body, any query
asking for a **table** (`scalar`) or a **graph** (`time_series`) failed
with HTTP 500 if the result contained a JSON column. The logs list view
worked fine, which is why this went unnoticed. The smallest way to hit
it is a raw ClickHouse panel running `select * from
signoz_logs.logs_v2`.
**Why it happened:** we ask ClickHouse to send JSON columns as plain
text, but the driver reports that such a column needs a different Go
type — so the reader prepared the wrong kind of container and the read
failed. The driver only reports the correct type *after* the first row
has been read, which is too late for code that sets up its containers up
front. The original JSON work patched around this inside the logs-list
reader only; the connection setting that causes it is global, so the
other two readers stayed broken.
**The fix:** correct the reported type once, at the connection that sets
that option, so every reader gets a container that works and receives a
normal map. Concretely:
- `pkg/querier` no longer needs its own workaround — the three readers
are back to ordinary code.
- The older v3/v4 read paths had the same bug and are fixed without any
changes of their own.
- A JSON path value such as `body_v2.level` now comes back as `"error"`
or `7` instead of a driver wrapper object.
- Grouping a graph by the whole JSON body used to collapse every group
into a single unlabelled line; each document now labels its own series.
#### Issues closed by this PR
Fixes https://github.com/SigNoz/engineering-pod/issues/5911
#### Additional Information
Verified end to end against a local stack with 1,000,000 log rows and
200,002 distinct `trace_id`s:
| query | before | after |
| --- | --- | --- |
| table query over a JSON column | 500 | 200, body returned as an object
|
| graph grouped by the JSON body | 500 | 200, 22 series, one per
document |
| graph grouped by `trace_id` (200k groups) | 200 | 200, unchanged |
A follow-up PR stacked on this one reworks how the graph reader
classifies columns — fixing boolean and small-integer columns in raw SQL
panels and cutting the reader's allocations.
Known gaps, unchanged from `main` and out of scope here:
- Waterfall and flamegraph read rows into structs, which this fix does
not cover. Moving span attributes to JSON will need the same type on
those fields, and one helper there fails silently rather than erroring.
- Dashboard variable queries no longer crash on a JSON column but still
reject it as an unsupported value type.
- A `Map(String, JSON)` column **panics inside the driver**, which can
take the process down. Confirmed still unfixed on `clickhouse-go` main,
and not yet reported upstream.
#### Description
- Update the sample span JSON in the LLM Observability attribute-mapping
Test tab to use OpenInference-style attribute keys (`llm.model_name`,
`llm.provider`, `llm.token_count.*`, `input.value`, `output.value`)
instead of the earlier mix of `gen_ai.*` and placeholder `my_company.*`
keys.
- The sample is what users see first when trying out attribute mapping,
so it should reflect the attribute shape they'll actually be mapping
from.
#### Issues closed by this PR
Closes -
https://github.com/orgs/SigNoz/projects/39/views/20?pane=issue&itemId=238442542&issue=SigNoz%7Cengineering-pod%7C5997
#### Screenshots / Screen Recordings
<img width="911" height="572" alt="image"
src="https://github.com/user-attachments/assets/88df20e9-0131-415c-9770-67ea8196075a"
/>
#### Additional Information
- Constant-only change (`SAMPLE_SPAN_JSON` in `spanInputStorage.ts`); no
parsing or mapping logic touched.
- Worth a sanity check that the new keys line up with the attribute
names the mapping UI is expected to suggest.
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
Bumping cloud integration agent version from v0.0.13 to v0.0.14
#### Contributes to
https://github.com/SigNoz/platform-pod/issues/3038
## Description
- The promql engine traces every evaluation through the global tracer
provider under an unnamed scope: `promqlExec`, `promqlPrepare`,
`promqlExecQueue`, and one `promqlInnerEval eval *promql.<Node>` span
per AST node per query. These didn't find much useful as the bottleneck
is usually the CH so we remove them.
#### Description
- Adds `GET|POST /prometheus/api/v1/query_range` and
`/prometheus/api/v1/query` (`pkg/prometheus/promapi`), following the
Prometheus HTTP API contract: float-unix or RFC3339 times, float-seconds
or duration-string durations, the `{status, data, errorType, error,
warnings, infos}` envelope with Prometheus' status codes, and the
11,000-point cap.
- The `/prometheus` prefix works as a drop-in Prometheus base URL:
Grafana's Prometheus data source, promtool, and the PromQL compliance
tester append `/api/v1/*` to a base URL, so they can point at SigNoz
unmodified. Same layout as Mimir/Cortex.
- Wired through `signoz.Handlers` (`prometheus.Handler` interface,
constructed in `NewHandlers`) like the other domain handlers.
- Range queries serve through the `RangeExecutor` capability when the
provider has it, so a clickhousev2-serving deployment transpiles through
these endpoints too.
- New `promapiconformance` integration suite: the frozen promqltest
corpus replayed against these endpoints with `prometheus::provider:
clickhousev2` — the two paths nothing else exercises (v2 as serving
provider, and this API surface). Instant cases go through `/query` with
a real `time` parameter. The `instant-coarse` corpus variants are
skipped — they exist only to encode instant evals as coarse ranges for
the v5 API, and their transpiled coarse-step serving is already covered
and ledgered by promqlconformance's clickhousev2 leg — so this suite
asserts zero divergences with no ledger of its own.
- Purely additive: the existing `GET /api/v1/query_range` and `GET
/api/v1/query` handlers are untouched. `openapi.yml` is generated and
these mux-registered routes are outside the generator, so their
documentation is the upstream Prometheus API contract they follow.
#### Additional Information
Final slice of the clickhouseprometheusv2 stack (#12323, #12324, #12325
— merged). Legacy endpoint removal, if ever, is a separate change after
usage drains.
#### Description
Registers a new per-user preference `log_details_pinned_attributes` in
`pkg/types/preferencetypes`, following the same shape as the existing
`span_details_pinned_attributes` (trace-details pin feature, #11092).
#### Description
- A PromQL subquery without a step, for example
`max_over_time(metric[5m:])`, segfaulted the whole query-service. The
engine calls `NoStepSubqueryIntervalFn` for such subqueries, and we
build the engine without it, so the call hits a nil function.
- The bug is present on every PromQL surface, because all of them share
the one engine constructor in `pkg/prometheus/engine.go`: v3 and v5
`query_range`, `/api/v1/query`, the clickhousev2 transpiler, and promql
alert rules. A saved rule with such a subquery crash-loops the instance
on its own schedule.
- The fix sets the callback to 1m. This matches the Prometheus default
global `evaluation_interval`, which upstream wires into this field. One
place fixes every path.
- This is the root cause of the SigNoz/platform-pod#3068 incident. The
instance-hardening request from that incident is tracked in
SigNoz/pulse-pod#308.
#### Issues closed by this PR
ClosesSigNoz/platform-pod#3068
#### Additional Information
We audited `EngineOpts` for more bugs of the same class.
`NoStepSubqueryIntervalFn` is the only field the engine calls without a
nil guard; `promql.NewEngine` defaults the other nil-able fields
(`Parser`, `FeatureRegistry`). The remaining gaps against upstream
wiring are not crashes, and we filed them separately:
SigNoz/pulse-pod#305 (`@` modifier and negative offset disabled),
SigNoz/pulse-pod#306 (engine self-metrics not registered),
SigNoz/pulse-pod#307 (active query tracker startup panic risk),
SigNoz/pulse-pod#309 (step guard in the v3 cache), SigNoz/pulse-pod#310
(upstream proposal to fail fast on the nil callback).
Tests for the bug:
- `pkg/prometheus/engine_test.go` — fails with the exact segfault when
the fix is removed.
- `tests/integration/tests/promqlconformance/04_no_step_subquery.py` — a
step-less subquery through `/api/v5/query_range` returns correct values
on both providers, and the service stays up.
- `tests/integration/tests/alerts/04_promql_subquery_no_step.py` — a
promql alert rule with a step-less subquery evaluates and fires.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
#### Description
- Deployment host routes (`GET`/`PUT /api/v2/zeus/hosts`) now use
`CheckResources` + `ResourceDef` instead of the coarse
`ViewAccess`/`AdminAccess` gates — per-resource FGA checks on
enterprise, role gate on community.
- New `deployment-host` metaresource kind with `list`/`update` verbs —
the GET returns the deployment's host collection and the PUT upserts the
single editable host. Admins get `list`+`update`, editors and viewers
get `list`, preserving current behavior.
- Migration `118_add_deployment_host_tuples` backfills the tuples for
existing organizations and re-syncs the stored managed-role transaction
groups; new organizations get both from the registry at bootstrap.
- Regenerated OpenAPI spec and transaction-groups schema: the operations
advertise `deployment-host:list`/`deployment-host:update` scopes instead
of `VIEWER`/`ADMIN`.
- Added `deploymenthost/01_authz.py` covering managed-role gating,
custom-role `list`/`update` grants, and rejection of verbs the resource
does not support.
#### Issues closed by this PR
ClosesSigNoz/platform-pod#2652
#### Description
- The `shows PermissionDeniedCallout in Keys tab when list-keys
permission is denied` test intermittently failed in CI: the
`fireEvent.click` on the Keys tab races with the nuqs testing adapter,
which can abort the queued `tab=keys` URL update mid-flight, leaving the
drawer stuck on the Overview tab.
- Since the tab is URL state, the test now lands directly on the Keys
tab via initial search params (`{ account: 'sa-1', tab: 'keys' }`),
avoiding the userEvent/click interaction altogether. The click-to-Keys
flow remains covered in `ServiceAccountDrawer.test.tsx`.
#### Issues closed by this PR
closesSigNoz/platform-pod#3053
#### Description
- Changes `GET /api/v1/features` from `ViewAccess` to `OpenAccess` in
both editions so every authenticated user, including those on custom
roles, can read feature flags.
- Feature flags describe the org's plan, not the caller's privileges,
and the frontend needs them to boot. With #12700 making the active
license readable by every authenticated user, the flags must be readable
too — otherwise custom-role users load the license but hang on the flags
fetch.
- Applies the same change to the flagger endpoint `GET /api/v2/features`
so the v2 client behaves identically when the frontend migrates to it.
#### Issues closed by this PR
Closes: https://github.com/SigNoz/platform-pod/issues/2653
#### Description
- The logs frequency chart now draws with the uPlotV2 `BarChart` instead
of the Chart.js `Graph`, putting it on the same chart stack as the rest
of the product. Covers both Logs Explorer and Live Logs, which share the
component.
- Chart setup moves into a `useLogsExplorerChartConfig` hook built on
the shared `buildBaseConfig`. Severity colours, labels, drag-to-zoom and
timezone handling all behave as before; stacking goes through
`stack`/`StackMode`.
- Timestamps now convert ns → s, since uPlot's x scale is in seconds
where Chart.js wanted ms.
- Removes the `.ant-card-body` rules from three stylesheets. They
stopped matching anything when #8904 dropped the antd Card wrapper.
- Unblocked by #12627, which removed the last-minute trim from the time
scale. My earlier attempt at that (#12528) is closed.
#### Issues closed by this PR
Closes -
https://github.com/orgs/SigNoz/projects/39/views/20?pane=issue&itemId=128310734&issue=SigNoz%7Csignoz%7C9059
#### Screenshots / Screen Recordings
https://github.com/user-attachments/assets/b27f75ae-085d-410b-a5b8-491bac590fd0
#### Additional Information
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- Adds integration tests that pin the semconv family matrix on a live
stack, in a dedicated package that runs SigNoz with
`resolve_semconv_families` on, plus a second instance with the flag at
its default. Part of #6143.
- The fleet has one identity per state: OLD (old spelling only), NEW
(current only), BOTH (current `staging` and old `production` — the
conflict row), NEITHER (keyless).
- 36 filter cells (nine operators × both spellings × both contexts): the
result sets do not depend on the requested spelling, the current
spelling wins on the conflict row, and negative operators keep keyless
rows exactly like a single key.
- Singles: group-by merges the fleet and echoes the requested spelling
in the group column; a bare name with the family under two contexts
warns and keeps the resource side; logs stay literal with the flag on;
everything stays literal with the flag off.
- The suite is registered in the `integrationci` matrix. Verified: 42
passed against the live stack, on postgres and on sqlite.
#### Additional Information
- Stack: #12441 (merged) → #12442 (merged) → #12443 (merged) →
**#12444**. Rebased on main after the #12443 merge.
- The earlier `13_semconv_evolution.py` in this branch is replaced: its
`!=` expectations omitted keyless rows, which contradicts the contract
pinned by `queriercommon/06_keyless_semantics.py`.
## Pull Request
---
### 📄 Summary
follow-up for #12027. Span-list trace-aggregate filtering ships in
#12122.
Adds `scalar` and `time_series` request types to `builder_ai_query`. The
`trace.` prefix selects the aggregation domain: trace aggregates use a
native CTE pipeline, while span aggregates delegate to the standard
traces builder with the qualification gate applied.
Trace-level filters qualify entire traces across both domains using the
standard filter pipeline. Grouping, `HAVING`, ordering, and limits match
the traces builder, including whole-window ranking for grouped time
series and top-N limits for scalar queries.
`count(trace.trace_id)` counts every AI trace, matching the trace list;
token aggregates average over traces that have token data (standard
`NULL` semantics, same as span-attribute aggregations elsewhere).
Includes SQL golden tests, rewrite unit tests, and integration coverage
for both domains, qualification, grouping, limits, bucketing, variables,
and targeted `400` errors.
#### Issues closed by this PR
Fixes https://github.com/SigNoz/engineering-pod/issues/5602
Fixes https://github.com/SigNoz/engineering-pod/issues/5603
---
### ✅ Change Type
_Select all that apply_
- [x] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated: ✅
- Manual verification:
- Edge cases covered:
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: None
- Potential regressions:
- Rollback plan:
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
---
## 👀 Notes for Reviewers
Still in testing phase
---
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
- forks the Traces Explorer into the AI Observability Explorer tab,
replacing the "Explorer coming soon" placeholder. All four views land:
list, trace, timeseries and table.
- the copied code is kept identical to the traces original on purpose —
same variable names, same `LOCALSTORAGE` keys, same analytics events,
same `DataSource.TRACES`. Only the folder layout differs. Divergence
(GenAI columns, AI query surface) comes in follow-ups, so this stays a
clean base to diff against.
- shared modules are imported, not duplicated:
`TracesExplorer/TracesTable`, `TracesExplorer/Controls`,
`TracesExplorer/explorerUtils`, `TracesExplorer/ListView/utils` and
`pages/TracesExplorer/aiActions`.
- both table views therefore use the shared TanStack table, so the AI
explorer starts out with resizable/reorderable columns rather than the
old antd `ResizeTable`.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes -
https://github.com/orgs/SigNoz/projects/39/views/20?pane=issue&itemId=223107878&issue=SigNoz%7Cengineering-pod%7C5843
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
https://github.com/user-attachments/assets/6f3bb336-f555-4345-aeca-ba061401f5af
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- **Stacked on #12672.** The first three commits are that PR
cherry-picked, since the trace view fork depends on its `FieldCell`
trace_id handling and the optional `columnStorageKey` /
`respectColumnOrder` props. Review only the last commit here; rebase
drops the rest once #12672 lands.
- `LLMObservability.test.tsx` now stubs `Explorer` the same way it
already stubs `DashboardContainer` — the real toolbar calls
`useNavigationType`, which needs a data router that integration test
does not mount.
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
* adds `ai_observability` to saved view for ai explorer
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5955
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
Column for exponential histograms is not decided by samples tables so it
should not run for exp histogrms
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/295
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
This adds a bunch of E2E tests for alerts, to test v1/v2 create and
edit, and also tests for alert history.
This started as tests only for history but decided to just add tests for
everything, while creating them, I found two bugs inside alerts, so they
already helping us before even landing :)
The changes in the UI are only to add testIds, no change in logic (and
no fix for the incidents)
| Scope | Before (`main`) | After (this branch) | Delta |
|---|---:|---:|---:|
| Alerts E2E tests | 2 | 191 | **+189** |
| Alerts E2E spec files | 1 | 31 | +30 |
| Whole E2E suite | 141 | 330 | **+189** |
#### Alerts page shell (7)
| File | Test | Status |
|---|---|---|
| `page.spec.ts` | AL-01 all four top-level tabs render | |
| `page.spec.ts` | AL-02 default tab is Alert Rules | |
| `page.spec.ts` | AL-03 tab switch writes ?tab= and clears subTab | |
| `page.spec.ts` | AL-04 Configuration deep-link | |
| `page.spec.ts` | AL-05 Triggered Alerts tab smoke | |
| `page.spec.ts` | AL-06 Notification Channels tab smoke | |
| `page.spec.ts` | AL-07 tab state survives reload | |
#### Alert rules list (19)
| File | Test | Status |
|---|---|---|
| `list/columns.spec.ts` | LR-01 renders all default columns (Status,
Alert Name, Severity, Labels, Actions) | |
| `list/columns.spec.ts` | LR-02 shows empty state when no rules exist |
skipped |
| `list/columns.spec.ts` | LR-10 column selector hides and shows a
column | |
| `list/navigation.spec.ts` | LR-11 row click opens the overview page |
|
| `list/navigation.spec.ts` | LR-12 ctrl/cmd-click opens the overview in
a new tab | |
| `list/navigation.spec.ts` | LR-13 actions menu Edit and Edit in New
Tab navigate correctly | |
| `list/navigation.spec.ts` | LR-17 New Alert button navigates to alert
creation | |
| `list/navigation.spec.ts` | LR-18 shows ErrorEmptyState when list
fails to load | skipped |
| `list/pagination-sort.spec.ts` | LR-07 navigates between pages | |
| `list/pagination-sort.spec.ts` | LR-08 changes page size | |
| `list/pagination-sort.spec.ts` | LR-09 sorts by column header click |
|
| `list/row-actions.spec.ts` | LR-14 Disable then Enable toggles the
rule state | |
| `list/row-actions.spec.ts` | LR-15 Clone creates a copy and shows
success toast | |
| `list/row-actions.spec.ts` | LR-16 Delete removes the rule and shows
success toast | |
| `list/search.spec.ts` | LR-03 filters by name | |
| `list/search.spec.ts` | LR-04 filters by severity and by label | |
| `list/search.spec.ts` | LR-05 shows no-results state with clear button
| |
| `list/search.spec.ts` | LR-06 resets pagination when searching | |
| `list/search.spec.ts` | LR-19 state and severity filters intersect,
they do not union | |
#### Create alert (52)
| File | Test | Status |
|---|---|---|
| `create/edge.spec.ts` | CE-04 a server-side rejection opens the error
modal and keeps the draft | |
| `create/edge.spec.ts` | CE-07 none of the four builder mounts logs a
console error | |
| `create/edge.spec.ts` | CE-09 the v2 Discard button is clickable |
skipped |
| `create/prefill.spec.ts` | CD-01 a compositeQuery alone selects the
alert type | |
| `create/prefill.spec.ts` | CD-02 thresholds prefill from JSON, and a
malformed value falls back | |
| `create/prefill.spec.ts` | CD-03 matchType and compareOp aliases
normalise to the enum | |
| `create/prefill.spec.ts` | CD-04 ruleName and yAxisUnit apply once and
never stomp an edit | |
| `create/prefill.spec.ts` | CD-05 evaluationWindowPreset=meter switches
to the cumulative daily window | |
| `create/prefill.spec.ts` | CD-06 URL prefill is ignored in edit mode |
|
| `create/shell.spec.ts` | CS-01 bare /alerts/new lists exactly the
expected alert-type cards | |
| `create/shell.spec.ts` | CS-02 picking a card writes both params and
mounts the v2 builder | |
| `create/shell.spec.ts` | CS-03 the anomaly card rewrites the rule
type, not the alert type | conditional |
| `create/shell.spec.ts` | CS-04 modifier-clicking a card opens the
builder in a new tab | |
| `create/shell.spec.ts` | CS-05 breadcrumb gains a third crumb after a
type is picked | |
| `create/shell.spec.ts` | CS-06 create renders inside the Alert Rules
tab and leaving drops subTab/search | |
| `create/shell.spec.ts` | CS-07 showClassicCreateAlertsPage=true
renders the v1 form instead | |
| `create/shell.spec.ts` | CS-08 Switch to Classic Experience replaces
history, so Back does not return to v2 | |
| `create/v1.spec.ts` | CV1-01 the classic form renders its steps and
the create-mode labels | |
| `create/v1.spec.ts` | CV1-02 the rendered severity is the default from
the rule, not the select | |
| `create/v1.spec.ts` | CV1-03 one keystroke in the name field is enough
to enable Save | |
| `create/v1.spec.ts` | CV1-04 Save stays disabled until the channel
configuration resolves | |
| `create/v1.spec.ts` | CV1-05 broadcast-to-all saves the rule with the
broadcast flag | skipped |
| `create/v1.spec.ts` | CV1-06 a cleared threshold is coerced to 0, so
the required-threshold branch is dead | |
| `create/v1.spec.ts` | CV1-07 cancelling the confirm dialog does not
save | |
| `create/v1.spec.ts` | CV1-08 the happy path posts the v1 body shape to
the shared endpoint | |
| `create/v1.spec.ts` | CV1-09 CV1-10 description, labels and severity
all land in the payload | |
| `create/v1.spec.ts` | CV1-11 test notification skips the dialog and
reports no matching data | |
| `create/v1.spec.ts` | CV1-12 with no channels the form is a dead end |
|
| `create/v1.spec.ts` | CV1-13 Cancel leaves the form without saving | |
| `create/v1.spec.ts` | CE-05 an empty PromQL expression is rejected
behind the dialog | |
| `create/v1.spec.ts` | CE-06 an empty ClickHouse query is rejected
behind the dialog | |
| `create/v1.spec.ts` | CV1-14 the condition sentence keeps its
selections | |
| `create/v2.spec.ts` | CV2-01 initial state: one critical threshold,
both actions gated | |
| `create/v2.spec.ts` | CV2-02 the save tooltip walks from the name gate
to the channel gate | |
| `create/v2.spec.ts` | CV2-03 clearing a threshold label re-gates the
save | |
| `create/v2.spec.ts` | CV2-04 a label added in the header survives the
save round-trip | |
| `create/v2.spec.ts` | CV2-05 a rejected label key surfaces as a
notification, not an inline message | |
| `create/v2.spec.ts` | CV2-06 CV2-07 the operator and match-type
selects offer the documented options | |
| `create/v2.spec.ts` | CV2-08 the operator is rule-wide: one change
reaches every threshold | |
| `create/v2.spec.ts` | CV2-09 CV2-10 added thresholds take preset
tiers, and the first cannot be removed | |
| `create/v2.spec.ts` | CV2-11 a channel on one threshold is not enough
— the validator loops all of them | |
| `create/v2.spec.ts` | CV2-12 the unit select is disabled while the
query has no y-axis unit | |
| `create/v2.spec.ts` | CV2-13 the recovery threshold control is never
rendered | |
| `create/v2.spec.ts` | CV2-14 CV2-15 the evaluation window and cadence
reach the payload | |
| `create/v2.spec.ts` | CV2-18 with no channels the dropdown offers only
a way to create one | |
| `create/v2.spec.ts` | CV2-19 routing policies unlock the save with
zero channels | |
| `create/v2.spec.ts` | CV2-16 the group-by select is disabled until the
query groups by something | |
| `create/v2.spec.ts` | CV2-17 repeat notifications enable their inputs
and reach the payload | |
| `create/v2.spec.ts` | CV2-20 happy-path save posts the v2 shape and
lands on the list | |
| `create/v2.spec.ts` | CV2-21 test notification reports that a
non-firing rule matched nothing | |
| `create/v2.spec.ts` | CV2-22 discard leaves without posting and resets
the form | |
| `create/v2.spec.ts` | CV2-23 every footer button is disabled while the
save is in flight | |
#### Edit alert (22)
| File | Test | Status |
|---|---|---|
| `edit/edge.spec.ts` | CE-03 an unknown ruleId shows AlertNotFound on
both entry URLs | |
| `edit/edge.spec.ts` | CE-03b /alerts/edit with no ruleId also lands on
AlertNotFound | |
| `edit/v1.spec.ts` | EV1-01 the classic form renders in edit mode
inside the details shell | |
| `edit/v1.spec.ts` | EV1-02 every seeded field prefills the form | |
| `edit/v1.spec.ts` | EV1-03 preferredChannels decide which channel
control is prefilled | |
| `edit/v1.spec.ts` | EV1-04 the happy-path update PUTs the v1 body and
keeps unrelated params | |
| `edit/v1.spec.ts` | EV1-05 Discard leaves without a PUT and without
changing the rule | |
| `edit/v1.spec.ts` | EV1-06 the header title and the form name field
agree | |
| `edit/v1.spec.ts` | EV1-07 /alerts/edit redirects for a v1 rule
exactly as it does for v2 | |
| `edit/v1.spec.ts` | EV1-08 editing a v1 rule never migrates it to the
v2 schema | |
| `edit/v2.spec.ts` | EV2-01 the v2 editor renders inside the details
shell | |
| `edit/v2.spec.ts` | EV2-02 name and labels prefill from the rule | |
| `edit/v2.spec.ts` | EV2-03 both thresholds prefill, and the sentence
reads spec[0] | |
| `edit/v2.spec.ts` | EV2-04 the recovery threshold control never
renders | |
| `edit/v2.spec.ts` | EV2-05 the evaluation window prefills, and a
non-preset value collapses to custom | |
| `edit/v2.spec.ts` | EV2-06 repeat notifications prefill from the
seeded renotify block | |
| `edit/v2.spec.ts` | EV2-07 alertOnAbsent prefills the advanced options
| |
| `edit/v2.spec.ts` | EV2-08 the evaluation cadence always reads back in
default mode | |
| `edit/v2.spec.ts` | EV2-09 changing a threshold PUTs the rule and the
change survives a reload | |
| `edit/v2.spec.ts` | EV2-10 the footer save is what persists a rename
made on the Overview tab | |
| `edit/v2.spec.ts` | EV2-11 Discard leaves without a PUT and without
touching the rule | |
| `edit/v2.spec.ts` | EV2-12 /alerts/edit is a legacy alias that
redirects into the details shell | |
#### Alert details (15)
| File | Test | Status |
|---|---|---|
| `details/actions.spec.ts` | AD-06 enable/disable toggle changes the
rule state | |
| `details/actions.spec.ts` | AD-07 Duplicate creates a copy and
navigates to overview | |
| `details/actions.spec.ts` | AD-08 Delete removes the rule and returns
to the list | |
| `details/chrome.spec.ts` | AD-09 copy-link button copies the current
URL to clipboard | conditional |
| `details/chrome.spec.ts` | AD-10 breadcrumb navigates back to the
alert list | |
| `details/chrome.spec.ts` | AD-13 document title updates to show the
rule name | |
| `details/header.spec.ts` | AD-01 v2 header shows editable name input
without Rename menu item | |
| `details/header.spec.ts` | AD-02 v1 header shows static title with
state, severity and labels | |
| `details/not-found.spec.ts` | AD-11 invalid ruleId shows AlertNotFound
page | |
| `details/not-found.spec.ts` | AD-12 missing ruleId on overview shows
AlertNotFound page | |
| `details/rename.spec.ts` | AD-03 v1 rename via modal updates the rule
name | |
| `details/rename.spec.ts` | AD-04 v2 inline rename saves via Overview
footer button | |
| `details/tabs.spec.ts` | AD-05 Overview/History tabs preserve ruleId
and relativeTime | |
| `details/tabs.spec.ts` | AD-05b switching to History tab discards
other history params | |
| `details/threshold-persistence.spec.ts` | TC-02 edit page displays the
saved threshold value | |
#### Alert history (75)
| File | Test | Status |
|---|---|---|
| `history/cross-cutting.spec.ts` | AX-01 full deep-link with all params
is honoured in one load | |
| `history/cross-cutting.spec.ts` | AX-02 page reload preserves all
history params | |
| `history/cross-cutting.spec.ts` | AX-03 browser back/forward restores
correct table state | |
| `history/cross-cutting.spec.ts` | AX-04 no unhandled console errors
across full history session | |
| `history/cross-cutting.spec.ts` | AX-05 no request storm on mount
(exactly one call per endpoint) | |
| `history/cross-cutting.spec.ts` | AX-06 v1 and v2 schema rules both
render history correctly | |
| `history/cross-cutting.spec.ts` | AX-07 no legacy v1 history API calls
during full session | |
| `history/cross-cutting.spec.ts` | AX-08 history API endpoints carry
expected params | |
| `history/empty-and-errors.spec.ts` | AE-01 invalid filter expression
shows syntax error and recovers on fix | |
| `history/empty-and-errors.spec.ts` | AE-02 empty filter_keys response
still mounts editor (no suggestions) | |
| `history/empty-and-errors.spec.ts` | AE-02b bogus ruleId never reaches
history APIs (shows AlertNotFound) | |
| `history/empty-and-errors.spec.ts` | AE-03 rule with no history
renders empty state (not error) | |
| `history/empty-and-errors.spec.ts` | AE-04 time range with no data
renders empty state | |
| `history/empty-and-errors.spec.ts` | AE-05 time-range change resets
pagination to first page | |
| `history/empty-and-errors.spec.ts` | AE-06 absurd time range (90d)
still renders | |
| `history/empty-and-errors.spec.ts` | AE-07 disabled rule history is
still readable | |
| `history/empty-and-errors.spec.ts` | AE-08 deleted rule shows
AlertNotFound on revisit | |
| `history/expression-filter.spec.ts` | AF-06 key suggestions load on
page load | |
| `history/expression-filter.spec.ts` | AF-07 value suggestions fetch
from filter_values endpoint | |
| `history/expression-filter.spec.ts` | AF-08 value suggestions filter
client-side as user types | |
| `history/expression-filter.spec.ts` | AF-09 running equality
expression filters the table | |
| `history/expression-filter.spec.ts` | AF-10 running expression resets
pagination to first page | |
| `history/expression-filter.spec.ts` | AF-11 Run button re-fetches
unchanged expression | |
| `history/expression-filter.spec.ts` | AF-12 in-flight query can be
cancelled | |
| `history/expression-filter.spec.ts` | AF-13 threshold.name and
severity keys filter correctly | |
| `history/expression-filter.spec.ts` | AF-14 unknown key returns 200
with zero rows (not 500) | |
| `history/expression-filter.spec.ts` | AF-15 expression is lost on
Overview→History round-trip (known bug) | |
| `history/expression-filter.spec.ts` | AF-16 expression and state
filter compose in request | |
| `history/expression-filter.spec.ts` | AF-17 clearing expression
restores full unfiltered list | |
| `history/state-filter.spec.ts` | AF-01 All filter sends no state param
in request | |
| `history/state-filter.spec.ts` | AF-02 Fired filter sends state=firing
in request | |
| `history/state-filter.spec.ts` | AF-03 Resolved filter shows empty for
rule with no resolutions | |
| `history/state-filter.spec.ts` | AF-03b Resolved filter shows rows for
rule with resolutions | |
| `history/state-filter.spec.ts` | AF-04 deep-link ?timelineFilter=FIRED
starts on Fired tab | |
| `history/state-filter.spec.ts` | AF-05 changing state filter resets
pagination to first page | |
| `history/statistics.spec.ts` | AS-01 Total Triggered card shows the
firing count | |
| `history/statistics.spec.ts` | AS-02 Avg. Resolution Time card shows
"No Resolutions." when none exist | |
| `history/statistics.spec.ts` | AS-03 empty stats card never renders a
sparkline | |
| `history/statistics.spec.ts` | AS-03b sparkline present with a
multi-point series | skipped |
| `history/statistics.spec.ts` | AS-04 change-vs-past indicator shows
"no previous data" when unavailable | |
| `history/statistics.spec.ts` | AS-09 stats update when time range
changes | |
| `history/statistics.spec.ts` | AS-11 Avg. Resolution Time shows
formatted duration when resolutions exist | |
| `history/statistics.spec.ts` | AS-12 Total Triggered counts only
firing rows (not resolved) | |
| `history/timeline-graph.spec.ts` | AT-03 renders canvas with two
segments (inactive→firing) | |
| `history/timeline-graph.spec.ts` | AT-03b renders canvas with three
segments (inactive→firing→inactive) | |
| `history/timeline-graph.spec.ts` | AT-19 handles nodata state without
console errors | |
| `history/timeline-pagination.spec.ts` | AT-06 next page sends cursor
and shows different rows | |
| `history/timeline-pagination.spec.ts` | AT-07 prev page drops the
cursor from request | |
| `history/timeline-pagination.spec.ts` | AT-08 pagination buttons
disable at first and last page | |
| `history/timeline-pagination.spec.ts` | AT-09 browser back after
paging returns to previous page | |
| `history/timeline-pagination.spec.ts` | AT-10 deep-link ?page=2 loads
second page directly | |
| `history/timeline-pagination.spec.ts` | AT-11 default sort order is
ascending | |
| `history/timeline-pagination.spec.ts` | AT-12 sorting toggles order
and resets to first page | |
| `history/timeline-pagination.spec.ts` | AT-13 single page disables
both pagination buttons | |
| `history/timeline-pagination.spec.ts` | AT-21 all pages together cover
the complete row set | |
| `history/timeline-table.spec.ts` | AT-01 timeline section renders all
chrome elements | |
| `history/timeline-table.spec.ts` | AT-02 Top 5 Contributors tab is
disabled with Coming Soon indicator | |
| `history/timeline-table.spec.ts` | AT-04 table rows display state,
labels and formatted timestamp | |
| `history/timeline-table.spec.ts` | AT-05 footer shows correct row
range | |
| `history/timeline-table.spec.ts` | AT-14 row click does not navigate
away | |
| `history/timeline-table.spec.ts` | AT-15 row actions link navigates to
logs explorer | |
| `history/timeline-table.spec.ts` | AT-15b row actions link navigates
to traces explorer | |
| `history/timeline-table.spec.ts` | AT-16 metrics rule rows show
disabled action (no related links) | |
| `history/timeline-table.spec.ts` | AT-17 CREATED AT column respects
app timezone setting | |
| `history/timeline-table.spec.ts` | AT-18 state cell renders Firing,
Resolved, and No Data correctly | |
| `history/timeline-table.spec.ts` | AT-18b pending/recovering states
render blank (coverage gap) | skipped |
| `history/timeline-table.spec.ts` | AT-18c disabled state renders as
"Muted" (coverage gap) | skipped |
| `history/timeline-table.spec.ts` | AT-20 time-range boundaries
inclusive/exclusive (coverage gap) | skipped |
| `history/top-contributors.spec.ts` | AS-05 card displays max 3 rows
with count ratios | |
| `history/top-contributors.spec.ts` | AS-13 contributor bar width is
the count as a percentage of the total | |
| `history/top-contributors.spec.ts` | AS-06 "View all" button only
appears when more than 3 contributors | |
| `history/top-contributors.spec.ts` | AS-07 View-all drawer shows
paginated list of all contributors | |
| `history/top-contributors.spec.ts` | AS-07b drawer opens from deep
link with ?viewAllTopContributors=true | |
| `history/top-contributors.spec.ts` | AS-08 View-all click adds
?viewAllTopContributors=true to URL | |
| `history/top-contributors.spec.ts` | AS-10 contributor rows show
related-logs link for logs-based rules | |
#### Notification channels (1)
| File | Test | Status |
|---|---|---|
| `channels/edit.spec.ts` | NC-01 an edited recipient persists after
reload | |
#### Skipped tests
| Test | File | Kind | Reason |
|---|---|---|---|
| the v2 Discard button is clickable | `create/edge.spec.ts` | hard
`test.skip(` | Real bug: the button is not clickable. Test written, left
ready to flip. |
| broadcast-to-all saves the rule with the broadcast flag |
`create/v1.spec.ts` | hard `test.skip(` | Real bug: the broadcast flag
is not persisted. |
| sparkline present with a multi-point series |
`history/statistics.spec.ts` | `test.skip(true)` | Flaky by
construction: the sparkline only renders with more than one data point,
and whether the seeded ~2-minute window lands in one stats bucket or two
depends on where it falls relative to the bucket boundary. |
| pending/recovering states render blank |
`history/timeline-table.spec.ts` | `test.skip(true)` | Unreachable:
`pending` and `recovering` are transient states, and no fixture can
reliably catch a rule mid-transition. |
| disabled state renders as "Muted" | `history/timeline-table.spec.ts` |
`test.skip(true)` | Unreachable: a `disabled` history row is
policy-driven, and disabling a rule appends no row (verified). |
| time-range boundaries inclusive/exclusive |
`history/timeline-table.spec.ts` | `test.skip(true)` | Unreachable:
asserting a row exactly at `start` and one at `start-1ms` means
controlling row timestamps, but evaluation times are whatever the ruler
chose. |
| the anomaly card rewrites the rule type, not the alert type |
`create/shell.spec.ts` | conditional | Runs only where the
`ANOMALY_DETECTION` feature flag is active; it is off on this stack. |
| copy-link button copies the current URL to clipboard |
`details/chrome.spec.ts` | conditional | Runs on Chromium only —
Playwright grants `clipboard-read` nowhere else. |
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic
closure on merge.
Closes https://github.com/SigNoz/engineering-pod/issues/4917
---
### ✅ Change Type
_Select all that apply_
- [ ] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [x] 🧪 Test-only
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: Alerts
- Potential regressions: None, only test ids
- Rollback plan: Find and fix the issue specifically
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Maintenance |
| Description | We added more E2E tests for Alerts page. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [ ] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
Fixes the bug where user is not able to add a field key with same names
and context but different dataType.
- only columns that actually carry a dataType get a new key; at most
their width/order resets once and re-heals on interaction. selection is
stored as field objects so it's never affected
- shared code (options menu + field picker) so it applies to both logs
and traces
- added/updated unit tests for the logs column factory and the
options-menu reorder/remove
- Saved views are unharmed
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5962
#### Screen Recording
Before
https://github.com/user-attachments/assets/e160a7fd-f0f8-4cf0-bad5-27178f9e29e0
After
https://github.com/user-attachments/assets/f9baab26-d7d8-47b1-b953-3adb684c19df
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
- migrates the traces view from the antd `ResizeTable` to the shared
TanStack table, the same one list view uses now, so both views share the
renderer.
- updated `FieldCell` to handle for `trace_id` columns as well.
- columns are resizable and reorderable now in trace view as well. which
was not possible earlier
- toolbar always renders now (root spans note + download + prev/next),
so pagination doesn't disappear when data is loading
- removed the styled-components file for this view, layout is a css
module now
- tests added for both views
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/engineering-pod/issues/5052
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
https://github.com/user-attachments/assets/e0ad657e-a74e-41fa-badb-8dea40007701
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
This PR fixes the Prev and next buttons shifting down on click due to
loader .
- cause was recent [icon
migration](https://github.com/SigNoz/signoz/pull/11222) away from antd
which restyled the loader.
- removed the loader on these buttons. they already disable while
loading, so the spinner was redundant and it was what caused the shift
- moved the buttons from antd (`Button`/`Flex`/`Spin`) to the
`@signozhq/ui` button
- removed the styled-components file, layout is a css module now
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
https://github.com/SigNoz/engineering-pod/issues/5942
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
Before
https://github.com/user-attachments/assets/db76270b-60f3-441f-adad-96abec9dd04b
After
https://github.com/user-attachments/assets/bb844652-b274-4849-9d49-485080308484
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
- moved list view from antd `ResizeTable` to Tanstack table.
functionalities kept same.
- pulled out a reusable trace table. new shared table + per field column
builder. This is added to keep the table renderer common for both
ListView and Trace View because they do not need to be different. Trace
view will integrate this component in following stacked PR.
- two new override vars on `TanStackTableView` (header height, first
column header padding)
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/engineering-pod/issues/5052
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
https://github.com/user-attachments/assets/d3a75b38-7cf5-4ab0-a7b4-fce404a03e63
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- Touches the shared `TanStackTableView` component.. two new override
vars, defaults unchanged for other tables. cc. @H4ad
<!--Please delete paragraphs that you did not use before submitting.-->
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
- Lets users add a free-typed column in the logs explorer "Edit columns"
panel, even if the key is not in the fields suggestions (e.g. nested
body json paths). Logs only.
- Shows the typed value as an addable option when it is not already a
suggestion or added. Exact, case-insensitive name match.
- Value shows via the existing body-first lookup. Nothing new is sent to
the backend for logs.
- Changed the column key separator from `.` to `:` so a typed dotted
name cannot clash with a context key (e.g. `resource.severity_text`).
Old saved keys self-heal, no migration.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5877
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
https://github.com/user-attachments/assets/0e91bb00-4be5-4dc7-ad3e-0e005ee6eb6b
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
Value needs `use_json_body` on for nested body paths, else the cell is
empty. Array paths and a leading `body.` dont resolve on the frontend
for now.
<!--Please delete paragraphs that you did not use before submitting.-->
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
This bumps the version from 0.2.3 to 0.1.0 (which also requires the bump
in the design-token to latest version), the changes can be found at
https://github.com/SigNoz/components/releases/tag/v0.1.0
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5926
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
The main diffs is the breaking changes in the vars names, other than
that, we mainly added new features for the components instead of
changing their look/usage, so we can expect no breaking-change in the
behavior or UI.
About Triggered Alerts (with new rewrite version of combobox simple).
https://github.com/user-attachments/assets/18cb117b-9a24-428e-8f6b-7dbf5012f7ea
The combobox also now emits `undefined` in case you have `allowClear`
enabled, this does not affect existing usages:
https://github.com/user-attachments/assets/70ca146f-0145-46d7-bf51-57f93b973ce4
#### Description
- Charts take a `stack` prop (`none` | `normal` | `percent`) and hand it
to their config, which derives the fill bands, and for `percent` the
percentage y-axis and a 0–100 soft range. Callers stop computing bands
or transforming data — V1/V2 bar panels, Meter Explorer and Billing each
drop their `setBands` call and declare `stack` instead.
- Stacking is no longer bar-specific, so TimeSeries stacks too. The
upcoming area chart is built on TimeSeries and needs this.
- `percent` rescales each x-slice to its column total. Mixed-sign
columns divide by the signed total, so shares can fall outside 0–100 and
still sum to it; a column summing to zero yields zero. The percent range
is soft rather than hard so those out-of-band shares stay visible.
- Tooltips now report the pre-stack value, identically in every mode.
They used to recover it by subtracting the series below, which only
works while stacking is cumulative — `percent` discards the column
total, so the raw value cannot be derived from the plot's data at all.
- `stack` lives on the two chart prop types rather than the shared
config builder props, so the ~10 other consumers of that builder
(histogram, alert previews, infra metrics, …) never expose an option
they cannot honour.
No spec or API change: both bar panels still read the existing
`stackedBarChart` boolean and map it to `normal`/`none`. `percent` is
reachable from the chart layer but nothing selects it yet — that arrives
with the panel spec change.
#### Additional Information
- Behaviour outside dashboards should be unchanged, with one exception:
Meter Explorer and the V1 bar panel previously passed `seriesCount + 1`
when computing bands, emitting a trailing band pointing at a series that
does not exist (Billing passed the correct count). Deriving bands
centrally normalises all three.
- Thresholds still draw under `percent`, but no longer widen the scale —
they carry source-unit values, so one at 500ms would stretch a
percentage axis to 0–500.
- percent also swaps the unit to a percent formatter and sets *soft* 0–1
limits (they normalise to 0–1, we use 0–100). It applies those limits
only when the user set none; we always apply them, because our soft
limits come from `spec.axes` in the source unit and are meaningless once
values are normalised.
- Commits are split so each one builds and is reviewable on its own: the
stacking algorithm, the config derivation, the tooltip change, then the
chart/consumer migration.
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
- `UPlotScaleBuilder` was overriding the x-axis max with `endTime - 1
minute`, rounded down to the minute — behaviour carried over from the
legacy `getXAxisScale`.
- On short time windows the trimmed max lands at or before the min, so
the scale range is empty/inverted and the chart draws no data.
- Removes the trim so the requested `min`/`max` pass through as-is and
the scale always matches the selected time range.
- Updates the scale builder tests, including a case for a sub-minute
window.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes -
https://github.com/orgs/SigNoz/projects/39/views/20?pane=issue&itemId=231774376&issue=SigNoz%7Cengineering-pod%7C5902
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
Before -
https://github.com/user-attachments/assets/11ca2fa4-9a07-42eb-9d8d-3a42daf4cfe1
Now -
https://github.com/user-attachments/assets/0114fccd-a6ef-4717-8d1c-aa3faf820da7
#### Additional Information
- Only the uPlotV2 path changes
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- Removes the deprecated `POST /api/v1/service_accounts/{id}/roles` and
`DELETE /api/v1/service_accounts/{id}/roles/{rid}` routes, their HTTP
handlers, and the `DeprecatedPostableServiceAccountRole` type, now that
all consumers use `/api/v1/service_account_roles`.
- Keeps the `GET /api/v1/service_accounts/{id}/roles` listing endpoint.
- Regenerates `docs/api/openapi.yml` and the frontend client.
#### Issues closed by this PR
Closes SigNoz/platform-pod#2919
#### Additional Information
- Final step of the migration; the frontend (#12589) and
integration-test (#12590) consumer moves are already merged.
#### Description
- Auth domain routes (`/api/v2/auth_domains`) now use `CheckResources` +
`ResourceDef` instead of the coarse `AdminAccess` gate — per-resource
FGA checks on enterprise, admin role gate on community.
- Create and update also check `attach` on the roles the request's
`roleMapping` will grant at SSO login (mapped roles + default role,
`signoz-viewer` when unset, `role:*` when `useRoleAttribute` is on);
update additionally checks `detach` on the roles the stored mapping was
granting, since a `PUT` replaces the mapping.
- Migration `117_add_auth_domain_tuples` backfills the admin
`auth-domain` tuples for existing organizations and re-syncs the stored
managed-role transaction groups; new organizations get both from the
registry at bootstrap.
- Regenerated OpenAPI spec: the auth-domain operations advertise
`auth-domain:*` and `role:attach`/`role:detach` scopes instead of
`ADMIN`.
- Added `callbackauthn/05_authz.py` covering managed-role gating,
custom-role wildcard/instance grants, and the role-mapping attach/detach
checks.
#### Issues closed by this PR
ClosesSigNoz/platform-pod#2649
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
These are pending code that was supposed to be deleted after
Infrastructure Monitoring & Alert History adopt the QBv5.
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic
closure on merge.
Closes https://github.com/SigNoz/engineering-pod/issues/5117
Closes https://github.com/SigNoz/engineering-pod/issues/5116
---
### ✅ Change Type
_Select all that apply_
- [ ] ✨ Feature
- [ ] 🐛 Bug fix
- [x] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated: Yes
- Manual verification: -
- Edge cases covered: -
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: Query Builder
- Potential regressions: Deleting more code than needed
- Rollback plan: Revert the deletion.
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Maintenance |
| Description | N/A |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
This PR fixes the following issues:
- page not resetting to 1 when switch
- bug was only detected/present when coming from deep link
- page not resetting to 1 when page produces a offset higher than total
- you had to switch to hosts to be able to see data again
#### Screenshots / Screen Recordings (if applicable)
> Include screenshots or screen recordings that clearly show the
behavior before the change and the result after the change. This helps
reviewers quickly understand the impact and verify the update.
Before:
Issue with page not reseting to 1 when changing category (after
refresh):
https://github.com/user-attachments/assets/00872b38-1263-43c1-8322-64d31ee1ee6a
Issue with page outside the offset:
https://github.com/user-attachments/assets/5194fb2e-5af3-491b-baf7-b4aa3a330c83
---
After:
Issue with page not reseting to 1 when changing category (after
refresh):
https://github.com/user-attachments/assets/545e5914-c26f-4189-b15a-dc399bdee28b
Issue with page outside the offset:
https://github.com/user-attachments/assets/1b93d162-22a3-41c8-802e-aa2aa6012db9
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic
closure on merge.
Closes https://github.com/SigNoz/pulse-pod/issues/208
---
### ✅ Change Type
_Select all that apply_
- [ ] ✨ Feature
- [x] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🐛 Bug Context
> Required if this PR fixes a bug
Both issues are caused after the refactor to the new table component and
after joining the categories into single component (without
unmount/mount when switching categories).
#### Root Cause
> What caused the issue?
> Regression, faulty assumption, edge case, refactor, etc.
Lack of reset the page to 1, and no proper way to detect and reset page
to 1 when outside the boundaries.
#### Fix Strategy
> How does this PR address the root cause?
Reset to page 1 after switch category and also include hook on tanstack
to ensure we reset page to last when outside the params.
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated: Yes
- Manual verification: Yes
- Edge cases covered: -
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: Infrastructure Monitoring
- Potential regressions: -
- Rollback plan: Open a new PR to fix the issue
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Bug Fix |
| Description | We fixed two issues around pagination inside
Infrastructure Monitoring causing the page not resetting to 1 after
switch category or when offset is higher than total amount of items. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
#### Description
- Adds Grok Build, GitHub Copilot, Serilog, and GCP Integration to the
onboarding data source picker.
- Adds a runtime step under AWS Lambda → Traces, so the new Go SDK guide
is reachable alongside the auto-instrumentation layers.
- New `github-copilot.svg`; the other three reuse existing logos
(`grok`, `dotnet`, `gcp`).
#### Issues closed by this PR
ClosesSigNoz/signoz.io#3999ClosesSigNoz/signoz.io#3982ClosesSigNoz/signoz.io#3972ClosesSigNoz/signoz.io#3947ClosesSigNoz/signoz.io#3806
#### Description
- The v2 services module (`/api/v2/services`) needs no change here: it
renders a QBv5 filter expression and runs through the querier, so the
merged #12442 resolution covers it when the `resolve_semconv_families`
flag is on.
- This layer covers the services read paths that do not go through QBv5,
behind the same flag (default: disabled). Part of #6143.
- The v1 services endpoints (services list, top operations) build the
legacy resource sub-query: it merges family members with current-wins
precedence, keeps the trailing `''` so keyless rows stay in negative
filters, widens positive index hints to any member, and drops negated
hints for correctness.
- The dependency graph accepts every family spelling as a filter key;
each spelling targets the historical `deployment_environment` column.
- The reader evaluates the flag per request from the org in the request
claims. The legacy logs and traces v4 explorer paths stay literal. With
the flag off, every generated query is the same as main; tests pin this.
#### Additional Information
- Stack: #12441 (merged) → #12442 (merged) → **#12443** → #12444 → …
This layer bases on main.
- The quick-filter default change and the stored-row migration from the
earlier version of this layer are deferred to the rollout phase:
persisted rows cannot be gated by a flag.
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
- Fixes the wrong timestamp shown in the log details drawer on the
dashboard list panel.
- Enables the new log details drawer on dashboards.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5939
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
Before
<img width="1631" height="865" alt="dashboard before"
src="https://github.com/user-attachments/assets/ebfcab65-9d5e-4a71-bf50-b67cdfceba9a"
/>
After
<img width="1608" height="813" alt="dashboard after"
src="https://github.com/user-attachments/assets/80d3e7ad-ffe2-4b5d-9d42-647b06f2c44d"
/>
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
- Enables the new log details drawer on infra monitoring.
- Fixes filtering from the drawer on infra. Filters, including body and
nested-field filters that were not working.
[RCA](https://github.com/SigNoz/engineering-pod/issues/5937#issuecomment-5339825308)
This is now fixed since body and other keys are all rendered from the
same place which uses the same passed addQuery util from EntityLogs
- Group by only show on logs explorer page.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5937
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
Before
https://github.com/user-attachments/assets/da6cd2bf-4409-4362-af6b-f4871bf49005
After
https://github.com/user-attachments/assets/4967af5a-d3c9-4198-86a4-3b865983b7c2
2026-08-19 14:02:26 +00:00
2045 changed files with 65029 additions and 54350 deletions
- **Extended fixtures:** For features needing complex setup (seeded data, API calls, cleanup), import from domain-specific fixtures that extend `auth`. See [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) for the full pattern.
- `fixtures/alerts/alert-rules` — worker-scoped rule list + test-scoped rule factory
- `fixtures/alerts/alert-history` — extends alert-rules, adds history fixtures (waits on ruler evaluation)
```ts
// Alert list tests - need rules, no history
import { test, expect } from '../../../fixtures/alerts/alert-rules';
// Alert history tests - need evaluated history rows
import { test, expect } from '../../../fixtures/alerts/alert-history';
- **Self-contained state.** The bootstrap creates a fresh stack with **zero** dashboards / alerts / etc. — never assume pre-existing data. Two cleanup shapes are valid; pick based on the spec size:
- **Per-test `try / finally`** — small specs (~ <10 scenarios) where each test owns its data.
@@ -49,6 +49,7 @@ Don't try to start the stack yourself — it can take ~4 minutes on a cold build
- **The list pages render zero-state when the workspace is empty.** Many locators (search input, sort button, `new-dashboard-cta` testid, "All Dashboards" header) are absent in zero-state. A 30s timeout on those usually means the workspace was empty — seed first via `createDashboardViaApi`.
- **The "Enter dashboard name…" inline field is a `RequestDashboardBtn` (template-request feedback form), not a create flow.** Tests that try to use it to create a named dashboard will silently no-op. The only UI create paths are the "New dashboard" dropdown → "Create dashboard" (default name "Sample Title", see `DEFAULT_DASHBOARD_TITLE`) or "Import JSON".
- **Auth.** `tests/e2e/fixtures/auth.ts` logs in once per worker and caches `storageState` (cookies + localStorage with `AUTH_TOKEN`). For API-driven seeding/cleanup, use `authToken(page)` from `helpers/dashboards.ts` and pass `Authorization: Bearer <token>`. Never re-implement login.
- **Extended fixtures.** Domain-specific fixtures extend `auth` and add seeded data. Alerts uses `fixtures/alerts/alert-rules` (worker-scoped rule list, test-scoped factory) and `fixtures/alerts/alert-history` (extends alert-rules, waits on ruler evaluation). See [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) for the pattern. When a test fails on missing data, check if it imports the wrong fixture level.
- **Ant Design popovers** (sort menu, action menu) are click-toggle. The trigger element is often an inline `<svg>` with a `data-testid` — clicking it opens the popover; clicking it again closes. After selecting an option, the popover auto-closes. If a test interacts with the popover twice, wait for the menu items to be visible explicitly between toggles.
- **Artifacts.** Every failed test writes to `tests/e2e/artifacts/results/<test-slug>/` — the `error-context.md` accessibility snapshot is the fastest way to see what the page actually looked like when it failed.
- **Type-check.** After edits, run `npx tsc --noEmit -p tests/e2e/tsconfig.json` if it succeeds, or rely on `npx playwright test --list` to validate the spec parses.
@@ -112,6 +112,41 @@ These two folders look similar but mean different things:
Rule of thumb: if it's a `test.extend` fixture, put it in `fixtures/`. If it's a function you call explicitly (or a constant the function uses), put it in `helpers/`. If it's a static file the helpers read, put it in `testdata/`.
### Extended fixtures
For features needing complex setup (API-seeded data, ruler evaluation waits, cleanup), create domain-specific fixtures that extend `auth`. Group them in `fixtures/<domain>/`.
**Fixture scopes:**
- **test scope** — fresh data per test. Use for mutations (edit, delete, rename).
- **worker scope** — shared across tests in one worker. Use for read-only data. Worker scope pays the setup cost once per worker instead of once per test.
1.**Identify scope** — Will tests mutate the data? If yes, test-scoped. If read-only, worker-scoped.
2.**Group by domain** — Put fixtures in `fixtures/<domain>/`. Helpers in `helpers/<domain>/`.
3.**Extend existing fixtures** — Chain from `auth` or another fixture to inherit its setup.
4.**Handle timeouts** — Worker-scoped fixtures that wait on backend processing need explicit timeouts.
5.**Clean up** — Always delete seeded data in the fixture teardown (after `use()`).
6.**Extract logic into functions** — Keep the `test.extend()` block lean; move setup/teardown logic to named functions so the extend block reads as a manifest of "what fixtures exist."
Each spec follows these principles:
1.**Directory per feature**: `tests/e2e/tests/<feature>/*.spec.ts`. Cross-resource junction concerns (e.g. cascade-delete) go in their own file, not packed into one giant spec.
@@ -232,11 +267,14 @@ cd tests/e2e
# Single feature dir
npx playwright test tests/alerts/ --project=chromium
# Single sub-area
npx playwright test tests/alerts/history/ --project=chromium
# Single file
npx playwright test tests/alerts/alerts.spec.ts --project=chromium
npx playwright test tests/alerts/page.spec.ts --project=chromium
| `SIGNOZ_E2E_SEEDER_URL` | Seeder HTTP base URL — hit by specs that need per-test telemetry. |
Loading order in `playwright.config.ts`: `.env` first (user-provided, staging), then `.env.local` with `override: true` (bootstrap-generated, local mode). Anything already set in `process.env` at yarn-test time wins because dotenv doesn't touch vars that are already present.
Precedence in `playwright.config.ts`, lowest to highest: `.env` (user-provided, staging) → `.env.local` (bootstrap-generated, local mode) → whatever is already in `process.env`. The config parses both files itself and only fills in keys the environment does not already define, so exporting a variable always wins:
```bash
# runs against a locally served frontend, not whatever .env.local points at
SIGNOZ_E2E_BASE_URL=http://127.0.0.1:3301 pnpm test tests/alerts
```
This is deliberately not `dotenv.config({ override: true })`. That flag makes the *file* beat `process.env`, which silently discarded exported values — including the `SIGNOZ_E2E_BASE_URL` in `pnpm test:staging`, whenever a `.env.local` happened to exist.
"tooltip_ms_teams_url":"The URL of the Microsoft Teams [webhook](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498) to send alerts to. Learn more about Microsoft Teams integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/ms-teams/).",
"tooltip_google_chat_url":"The URL of the Google Chat space [incoming webhook](https://developers.google.com/workspace/chat/quickstart/webhooks) to send alerts to. It must be an https URL on chat.googleapis.com.",
"google_chat_webhook_url_invalid":"Webhook URL must be an https URL on chat.googleapis.com",
"field_jira_site":"Site URL",
"tooltip_jira_site":"Your Jira Cloud base URL, e.g. https://your-domain.atlassian.net. Only Jira Cloud is supported.",
"jira_site_invalid":"Site URL must be an https URL on an atlassian.net domain",
"jira_required_fields":"Site URL, email, API token, project and issue type are required",
"jira_service_account_tip":"Recommended: use a Jira service account so alerts aren't reported under a personal name and the channel keeps working when someone leaves.",
"jira_service_account_tip_link":"Learn how",
"field_jira_email":"Email",
"help_jira_email":"The Atlassian account email used for authentication.",
"field_jira_api_token":"API token",
"help_jira_api_token":"Create one at id.atlassian.com under Security → API tokens.",
"field_jira_project":"Project key",
"field_jira_issue_type":"Issue type",
"help_jira_issue_type":"An issue type that exists in the project, e.g. Task, Bug or Incident.",
"field_jira_summary":"Summary (issue title)",
"help_jira_summary":"Template for the Jira issue title.",
"field_jira_description":"Description",
"help_jira_description":"Template for the issue description. Rendered as rich text with a status panel and links back to SigNoz.",
"jira_advanced_section":"Advanced Options",
"field_jira_priority":"Priority",
"placeholder_jira_priority":"Leave empty to use the project default",
"help_jira_priority":"Must match a priority in the project's scheme, e.g. High.",
"field_jira_labels":"Labels",
"placeholder_jira_labels":"Type a label and press Enter",
"help_jira_labels":"signoz and a deduplication label are added automatically.",
"help_jira_resolve_transition":"When the alert resolves, SigNoz moves the Jira issue to a \"Done\" status via a workflow transition. This is auto-detected — leave it empty unless your project has more than one \"Done\" transition (e.g. Done vs. Won't Do) and you want to force a specific one by name.",
"help_jira_reopen_transition":"When a resolved alert fires again (within the reopen window), SigNoz moves the issue back out of \"Done\" to an active status via a workflow transition. This is auto-detected — leave it empty unless you want to force a specific one by name (e.g. To Do or Reopen).",
"placeholder_jira_resolve_transition":"Auto-detected, e.g. Done",
"placeholder_jira_reopen_transition":"Auto-detected, e.g. To Do",
"help_jira_wont_fix_resolution":"If the issue was resolved with this resolution (e.g. Won't Do), SigNoz won't reopen it when the alert fires again; a new issue is created instead. Leave empty to always reopen.",
"help_jira_reopen_duration":"If a resolved alert fires again within this window, the same ticket is reopened; after the window, a re-fire opens a new ticket instead. Default: 3d.",
"tooltip_jira_reopen_duration":"Accepted units: m (minutes), h (hours), d (days), w (weeks), y (years) — e.g. 30m, 72h or 3d. Minimum 1m.",
"jira_reopen_duration_invalid":"Reopen window must be a duration like 30m, 72h or 3d (minimum 1m)",
"jsmops_tip":"Create an API integration on your JSM team's Operations page and paste its key below.",
"jsmops_tip_link":"Learn how",
"field_jsmops_api_key":"API key",
"help_jsmops_api_key":"The JSM Ops integration API key, from your team's Operations → Integrations → API. Make sure the integration is turned on.",
"field_jsmops_message":"Message (alert title)",
"help_jsmops_message":"Template for the alert title. Truncated to 130 characters.",
"field_jsmops_description":"Description",
"help_jsmops_description":"Template for the alert description. Rendered as rich text; kept under 15,000 characters.",
"jsmops_advanced_section":"Advanced Options",
"field_jsmops_priority":"Priority",
"help_jsmops_priority":"Template resolving to one of P1–P5. Leave as-is to map from alert severity.",
"field_jsmops_tags":"Tags",
"placeholder_jsmops_tags":"Type a tag and press Enter",
"help_jsmops_tags":"Tags added to every alert.",
"incidentio_tip":"Create an HTTP alert source in incident.io (On-call \u2192 Alert routing \u2192 Sources) and paste its URL and token below.",
"incidentio_tip_link":"Learn how",
"field_incidentio_url":"Alert source URL",
"help_incidentio_url":"The alert events URL from the source's setup page, e.g. https://api.incident.io/v2/alert_events/http/<source_config_id>.",
"field_incidentio_token":"Token",
"help_incidentio_token":"The alert source's secret token, from the same setup page.",
"field_incidentio_title":"Title",
"help_incidentio_title":"Template for the alert title. Kept stable while the alert fires \u2014 incident.io ignores content updates on repeat events.",
"field_incidentio_description":"Description",
"help_incidentio_description":"Template for the alert description. Markdown, rendered natively by incident.io.",
"incidentio_required_fields":"Alert source URL and token are required",
"incidentio_url_invalid":"URL must be an incident.io alert events URL (https://api.incident.io/v2/alert_events/http/<source_config_id>)",
"help_incidentio_metadata":"Key-value pairs added to every alert's metadata, on top of the alert's labels (these win on a key clash). Values may use templates, e.g. {{ .CommonLabels.severity }}.",
"tooltip_ms_teams_url":"The URL of the Microsoft Teams [webhook](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498) to send alerts to. Learn more about Microsoft Teams integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/ms-teams/).",
"tooltip_google_chat_url":"The URL of the Google Chat space [incoming webhook](https://developers.google.com/workspace/chat/quickstart/webhooks) to send alerts to. It must be an https URL on chat.googleapis.com.",
"google_chat_webhook_url_invalid":"Webhook URL must be an https URL on chat.googleapis.com",
"field_jira_site":"Site URL",
"tooltip_jira_site":"Your Jira Cloud base URL, e.g. https://your-domain.atlassian.net. Only Jira Cloud is supported.",
"jira_site_invalid":"Site URL must be an https URL on an atlassian.net domain",
"jira_required_fields":"Site URL, email, API token, project and issue type are required",
"jira_service_account_tip":"Recommended: use a Jira service account so alerts aren't reported under a personal name and the channel keeps working when someone leaves.",
"jira_service_account_tip_link":"Learn how",
"field_jira_email":"Email",
"help_jira_email":"The Atlassian account email used for authentication.",
"field_jira_api_token":"API token",
"help_jira_api_token":"Create one at id.atlassian.com under Security → API tokens.",
"field_jira_project":"Project key",
"field_jira_issue_type":"Issue type",
"help_jira_issue_type":"An issue type that exists in the project, e.g. Task, Bug or Incident.",
"field_jira_summary":"Summary (issue title)",
"help_jira_summary":"Template for the Jira issue title.",
"field_jira_description":"Description",
"help_jira_description":"Template for the issue description. Rendered as rich text with a status panel and links back to SigNoz.",
"jira_advanced_section":"Advanced Options",
"field_jira_priority":"Priority",
"placeholder_jira_priority":"Leave empty to use the project default",
"help_jira_priority":"Must match a priority in the project's scheme, e.g. High.",
"field_jira_labels":"Labels",
"placeholder_jira_labels":"Type a label and press Enter",
"help_jira_labels":"signoz and a deduplication label are added automatically.",
"help_jira_resolve_transition":"When the alert resolves, SigNoz moves the Jira issue to a \"Done\" status via a workflow transition. This is auto-detected — leave it empty unless your project has more than one \"Done\" transition (e.g. Done vs. Won't Do) and you want to force a specific one by name.",
"help_jira_reopen_transition":"When a resolved alert fires again (within the reopen window), SigNoz moves the issue back out of \"Done\" to an active status via a workflow transition. This is auto-detected — leave it empty unless you want to force a specific one by name (e.g. To Do or Reopen).",
"placeholder_jira_resolve_transition":"Auto-detected, e.g. Done",
"placeholder_jira_reopen_transition":"Auto-detected, e.g. To Do",
"help_jira_wont_fix_resolution":"If the issue was resolved with this resolution (e.g. Won't Do), SigNoz won't reopen it when the alert fires again; a new issue is created instead. Leave empty to always reopen.",
"help_jira_reopen_duration":"If a resolved alert fires again within this window, the same ticket is reopened; after the window, a re-fire opens a new ticket instead. Default: 3d.",
"tooltip_jira_reopen_duration":"Accepted units: m (minutes), h (hours), d (days), w (weeks), y (years) — e.g. 30m, 72h or 3d. Minimum 1m.",
"jira_reopen_duration_invalid":"Reopen window must be a duration like 30m, 72h or 3d (minimum 1m)",
"jsmops_tip":"Create an API integration on your JSM team's Operations page and paste its key below.",
"jsmops_tip_link":"Learn how",
"field_jsmops_api_key":"API key",
"help_jsmops_api_key":"The JSM Ops integration API key, from your team's Operations → Integrations → API. Make sure the integration is turned on.",
"field_jsmops_message":"Message (alert title)",
"help_jsmops_message":"Template for the alert title. Truncated to 130 characters.",
"field_jsmops_description":"Description",
"help_jsmops_description":"Template for the alert description. Rendered as rich text; kept under 15,000 characters.",
"jsmops_advanced_section":"Advanced Options",
"field_jsmops_priority":"Priority",
"help_jsmops_priority":"Template resolving to one of P1–P5. Leave as-is to map from alert severity.",
"field_jsmops_tags":"Tags",
"placeholder_jsmops_tags":"Type a tag and press Enter",
"help_jsmops_tags":"Tags added to every alert.",
"incidentio_tip":"Create an HTTP alert source in incident.io (On-call \u2192 Alert routing \u2192 Sources) and paste its URL and token below.",
"incidentio_tip_link":"Learn how",
"field_incidentio_url":"Alert source URL",
"help_incidentio_url":"The alert events URL from the source's setup page, e.g. https://api.incident.io/v2/alert_events/http/<source_config_id>.",
"field_incidentio_token":"Token",
"help_incidentio_token":"The alert source's secret token, from the same setup page.",
"field_incidentio_title":"Title",
"help_incidentio_title":"Template for the alert title. Kept stable while the alert fires \u2014 incident.io ignores content updates on repeat events.",
"field_incidentio_description":"Description",
"help_incidentio_description":"Template for the alert description. Markdown, rendered natively by incident.io.",
"incidentio_required_fields":"Alert source URL and token are required",
"incidentio_url_invalid":"URL must be an incident.io alert events URL (https://api.incident.io/v2/alert_events/http/<source_config_id>)",
"help_incidentio_metadata":"Key-value pairs added to every alert's metadata, on top of the alert's labels (these win on a key clash). Values may use templates, e.g. {{ .CommonLabels.severity }}.",
* Returns a dashboard SigNoz ships and owns, addressed by its stable definition name (e.g. `ai-o11y-overview`) rather than its id. System dashboards are read-only and upgraded through releases. The dashboard's own `name` field carries a reserved prefix that the path segment must not include.
* This endpoint returns the sanitized v2-shape dashboard data for public access. Each panel query is reduced to a safe field subset, so filters and raw query strings are not exposed.
* Prometheus-compatible endpoint: the request and response contract is the upstream Prometheus HTTP API (https://prometheus.io/docs/prometheus/latest/querying/api/). Parameters are accepted as URL query parameters or a form-encoded body, on GET and POST alike.
* Prometheus-compatible endpoint: the request and response contract is the upstream Prometheus HTTP API (https://prometheus.io/docs/prometheus/latest/querying/api/). Parameters are accepted as URL query parameters or a form-encoded body, on GET and POST alike.
* Prometheus-compatible endpoint: the request and response contract is the upstream Prometheus HTTP API (https://prometheus.io/docs/prometheus/latest/querying/api/). Parameters are accepted as URL query parameters or a form-encoded body, on GET and POST alike.
* Prometheus-compatible endpoint: the request and response contract is the upstream Prometheus HTTP API (https://prometheus.io/docs/prometheus/latest/querying/api/). Parameters are accepted as URL query parameters or a form-encoded body, on GET and POST alike.
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.