#### Description
- The generated API client now sends array query params as repeated keys
(`states=firing&states=pending`) instead of axios' default `states[]=`.
The backend binds only repeated keys, so a `states` filter sent through
a generated hook was ignored and every rule came back.
- The alert list page and the home page alerts card drop their own
requests that worked around this.
- `states` on `GET /api/v3/rules` is the only array query param in the
generated and AI assistant clients today, so no other request changes.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/719
#### Description
- `ListAlertRules.views.test.tsx` now gives each test 15 s instead of
Jest's default 5 s. Other slow page tests use the same timeout.
- "deleting the active view falls back to All" is the slowest test in
the file, about 0.8 s locally. CI runs the file about 4x slower (48 to
52 s against 13 s locally), which pushes it past 5 s. It failed the
merge queue for [#13476](https://github.com/SigNoz/signoz/pull/13476)
and [#13301](https://github.com/SigNoz/signoz/pull/13301), and two
unrelated PRs.
- Each step in it finishes well inside the 1 s `waitFor` limit. Only the
total runs over, so this is load, not a stuck wait.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/726
<!--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
- using X-Forwarded-For header for client.address as audit log attribute
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
closes https://github.com/SigNoz/keystone-pod/issues/213
<!--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
- Audit query_range reads the new `signoz_audit` schema: `body`,
`scope`, `attributes` and `resource` are JSON columns, and the old typed
maps are gone.
- Audit skips the attribute and resource key tables, which the new
schema no longer has. Keys resolve from the intrinsic fields plus a
fixed list of the resource keys the audit writer emits, so bare resource
keys (e.g. `signoz.resource.kind`) work without the `resource.` prefix
until keys move to `signoz_metadata`.
- Raw audit rows return `resource` with flat dotted keys
(`resource.attributes["service.name"]`) instead of nesting at every dot.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
closes https://github.com/SigNoz/keystone-pod/issues/201
#### Description
- The All Endpoints and Endpoint Stats pages use `QuerySearch` for the
filter and `GroupByFilter` for the group-by, the query builder's own
components. This removes the last v3 autocomplete calls in API
monitoring.
- Each drawer tab keeps its filter in `QuerySearchV2Provider`, with its
own URL param (`apiMonitoringAllEndpointsExpression`,
`apiMonitoringEndpointStatsExpression`), the same way infra monitoring's
tabs do. The payload builders and the row-click drill-down work on the
expression directly, so the items conversion is gone. The filter applies
on Ctrl+Enter or when focus leaves the box.
- The group-by page no longer fetches keys or caches them to resolve
picks. `GroupByFilter` returns full keys, so about 80 lines of page
logic are gone.
- A Run button sits next to the filter on both pages. It applies the
filter the same way leaving the box does, and shows a spinner while that
tab's queries load.
- The filter shows the drawer's locked scope as a chip in front of the
text, like infra monitoring: `http_host = '<domain>' +1`, plus
`http_url` on Endpoint Stats. The chip is a new opt-in `scopeClauses`
prop on `QuerySearch`.
- Endpoint Stats and Top 10 Errors pick the endpoint from a searchable
select above the filter, not from text in the expression. The endpoint
is added to the request filter when the request is built.
- Removed the dead v4 fallback on the stats page and an unused
zero-state payload builder. Both have had no effect since November 2025.
#### Issues closed by this PR
Closes https://github.com/SigNoz/events-pod/issues/143
Closes https://github.com/SigNoz/events-pod/issues/144
#### Screenshots
sbshot:
https://claude.ai/artifact/YLourNcAvCqsmVbtnGhH2L?sk=bVmSGZMwZ7VSmSukZLyNAwhttps://github.com/user-attachments/assets/bf6e9930-4d14-40f7-ba66-fe36045eb4ef
url now carries filter exp
https://github.com/user-attachments/assets/3310000a-d03a-4a54-9119-dde748aa6dc7
#### Additional Information
- The filter applies on Ctrl+Enter or blur, not on every completed chip.
Free-form expressions (OR, NOT, functions) now work.
- Group-by keys store the v5 field context (`attribute` / `resource`)
where v3 wrote `tag`. Keys restored from the URL carry the name only; v5
resolves them by name.
- The group-by has no clear-all cross and no `+N` collapse because the
shared component has neither. Every selected key stays visible with its
own ×.
- The filter row on the stats page now grows with a multi-line
expression instead of overflowing.
- Links saved with the old items-shaped filters in `apiMonitoringParams`
still open. The list page moves those filters into the tab params once
and drops the old keys.
- The drawer starts from the list's filter when you open it by clicking
a domain. A drawer opened from a link uses only the filters in the link.
A cleared tab filter stays cleared across tab switches and reloads.
- The user's filter is joined to the page's base filter as `base AND
(filter)`, so an `OR` in the user's filter stays scoped to the domain
and endpoint.
- Clicking a grouped row whose group value is empty now drills down with
`key NOT EXISTS`. It used to send `key = null`, which matched nothing.
- A hand-typed `http_url = …` in the All Endpoints filter now carries
into Endpoint Stats as part of the user's filter. Before, opening a row
dropped it, so "All endpoints" showed every endpoint. Now the clause
still narrows the data and the endpoint list. Other `http_url` operators
behave as before.
- Fixed a URL bug: writes to `apiMonitoringParams` now build on the live
URL. Before, several writes in one render kept only the last one. On
main, picking a time range in the drawer dropped `selectedInterval` from
the URL.
- `QuerySearchV2Provider` now follows URL changes after mount. Before,
it read its URL param once and wrote its stored filter back over later
changes, so Back and Forward did not restore a filter. Using it here
exposed this. Infra monitoring's Logs, Traces, Events and Overview tabs
share the provider and get the same fix.
- The `QuerySearch` changes are opt-in props (`showRunButton`,
`isLoadingQueries`, `scopeClauses`). Its other callers, infra monitoring
included, render as before.
- Storybook: the existing drawer stories (Domain Endpoints, Endpoint
Stats, Service Filtered, Port Domain, Failing Domain, Top Errors)
already show the picker, the Run button and the chip. No story shows the
Run button busy.
- Part of https://github.com/SigNoz/events-pod/issues/156.
#### Description
Visual cleanup of the alert rules list, to match the dashboards list:
- Removed the gap between the tab bar and the list.
- Rows use one black background instead of alternating colors.
- Rule labels are neutral grey instead of red, so only firing state and
severity draw attention.
- Column header dividers show only on hover.
#### Additional Information
- The existing `Pages/Alerts/Rules` stories already render these states;
no story change needed.
#### Description
- Add Pixeltable and Claude Cowork cards to the onboarding data sources.
Pixeltable opens the trace-first guide. Claude Cowork opens the
log-event guide.
- Add a Pixeltable logo (`Logos/pixeltable.svg`). Pixeltable has no
vector logo, so I redrew its dot-matrix "P" on a 24x24 grid.
- Add the `python celery` search keyword to the Celery card. Searching
`python celery` returned no results before.
- Fix the built-in and AWS integration card links. The old
`/integrations?integration=...` links only opened the integrations list.
They now use `/integrations/builtin-<id>` and
`/integrations/aws?service=<id>`.
#### Issues closed by this PR
ClosesSigNoz/signoz.io#4487ClosesSigNoz/signoz.io#4482ClosesSigNoz/signoz.io#3279
<!--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
We don't want external clients like Terraform or AI Agents to be sending
these fields anymore cuz they are useless. That's why we need to remove
these from the schema. But sadly, we probably have users who send these
in their scripts, and our rules APIs don't accept fields not in the
struct, so these fields stay in the struct.
<!--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/731
#### Description
- The Terraform provider is generated from our OpenAPI spec. #13424
described the Jira channel `auth` as a `oneOf` (cloud or self-hosted)
with no discriminator, which the generator cannot map, so the
notification channel resource stopped building.
- `deployment` sits next to `auth`, not inside it, so it cannot act as
the discriminator.
- This PR describes `auth` in the spec as one object with optional
`email`, `apiToken` and `pat`. Each description says which deployment
needs it.
- The Go type now matches the spec: one auth struct replaces the two
per-deployment structs, and the fields are validated against
`deployment`.
- The accepted payload, the responses and stored channels stay the same,
so no migration is needed.
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#728
#### Additional Information
- Problem and options doc:
https://app.notion.com/p/415fcc6bcd1980a0869fd5ed2e19ca3c
- The spec no longer marks the auth fields as required per deployment.
The server still rejects a wrong or missing combination with a 400.
- A field of the other deployment (for example `pat` on a cloud channel)
now gets an explicit error message instead of the unknown field one. It
is rejected only when it is not blank.
- The generated spec and API types are in a separate commit.
#### Description
- `QuickFiltersLayout` and `QuerySection` in the AI Explorer tests.
Neither is under test, and rendering them made the tab-switch test take
~1s; it now takes ~0.1s.
- Assert the trace view and its URL columns before switching tabs, so
the cleared-columns check can't pass when the columns were never set.
- After the switch, assert the list panel type is in the URL and the
trace view unmounts.
#### Issues closed by this PR
#### Screenshots / Screen Recordings
#### Additional Information
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- API calls that nobody catches reach Sentry as unhandled rejections.
Most of these failures are expected: a 403 for a user without a viewer,
editor or admin role, a 401 for an expired session, or a network error
from a dropped connection.
- Since the axios 1.21.0 bump (#13339), these errors carry our call site
in the stack. Sentry opens a new issue for each call site, and each new
issue pages. SIGNOZ-UI-5N4 paged twice this way.
- `beforeSend` now drops axios errors with status 401 or 403, or with
code `ERR_NETWORK`. Sentry still gets 400, 404 and 5xx.
#### Issues closed by this PR
Closes https://github.com/SigNoz/events-pod/issues/261
#### Additional Information
- The filter matches raw axios errors only. A call that goes through
`ErrorResponseHandlerV2`, `ErrorResponseHandlerForGeneratedAPIs` or
`convertToApiError` rethrows an `APIError`. Sentry still reports that
error when nobody catches it.
- The filter drops all 403 and `ERR_NETWORK` errors. A missing
permission check in the UI, or a CORS or base-path regression, no longer
shows in Sentry. This is a deliberate choice.
- The filter also drops 401 errors from the token refresh retry path.
- A separate PR adds an error state for the empty key suggestions in the
query search bar.
#### Description
- `PUT /api/v2/quick_filters/{source}` (and the legacy v1 update) now
returns 400 for fields whose values are unique per span/log, so a quick
filter on them has nothing to pick from: trace/span/parent-span IDs
(incl. legacy `traceID`/`spanID`/`parentSpanID`), `timestamp`, log
`body`, `gen_ai.tool.call.id`, free-text payloads
(gen_ai/OpenInference/Langfuse/Traceloop messages,
`exception.message`/`stacktrace`, `db.statement`/`db.query.text`), token
counts (`gen_ai.usage.*`, `llm.token_count.*`, `ai.usage.*`, …) and the
per-span `signoz.gen_ai.*.cost` keys. The list is hard-coded in
`quickfiltertypes`.
- `trace_id` is removed from the default traces quick filters, including
the seeds in migrations `030`/`031` (separate commit), so `147` is a
no-op on fresh installs.
- Migration `147` strips these fields from every stored quick filter;
other entries are kept byte-for-byte.
#### Issues closed by this PR
Closes#13157
#### Additional Information
- Follows the approach agreed in SigNoz/platform-pod#3282, replacing the
frontend-only blocklist from #13182.
- Changelog: `trace_id`, `span_id` and other high-cardinality fields
(token counts, costs, message payloads) are no longer supported as quick
filters and are removed from existing quick filter settings; filter by
them from the search bar instead.
- The quick filter settings panel still lists these fields as
suggestions; adding one now surfaces the API error. Hiding them is out
of scope.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#### Description
- The alert rules State filter now uses the same select as the Created
by filter: checkbox options, one pill plus "+N", and the same background
and width. The placeholder reads "State" instead of "Filter by state".
- The Created by select moves into a shared `MultiSelectFilter` in
`components/ListFilter`, and both filters render through it. Created by
looks and behaves as before on the dashboards and alert rules lists.
- The State filter moves from `@signozhq/ui` `ComboboxSimple` to antd
`Select`, to match the antd Created by and Updated filters beside it.
`ComboboxSimple` can't run the query once on close or show checkbox
options yet (https://github.com/SigNoz/components/issues/483).
- New story on the alert rules page: the State dropdown open.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/720
#### Screenshots / Screen Recordings
Visual diff in dark and light:
https://claude.ai/artifact/UwA1RZA6VTDMEEbGpyVsvg
- 10 changed, all inside the State filter box on the alert rules list.
- 2 new: the State dropdown open.
- 8 pixel-identical: the dashboards list, where Created by is unchanged.
<!--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
- Get and delete check the path id. Bulk upsert checks each rule id and
falls back to the wildcard for creates and sync rows.
- Managed roles keep the same access as before: admin writes, editor and
viewer read. A migration backfills the tuples for existing orgs.
- Integration tests cover managed roles plus wildcard and rule-scoped
custom roles.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/nerve-pod/issues/463
<!--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
- using semantic conventions described in `signoz-semanatic-conventions`
repository for audit
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
contributes to https://github.com/SigNoz/keystone-pod/issues/188
<!--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
- add `kafka` auditor provider that produces audit logs to a
kafka/redpanda topic as `otlp_proto`
- uses franz-go v1.21.7.
- no delivery timeout or retry cap: on franz-go < v1.22.2 these can ack
a record that was never written (twmb/franz-go#1484).
- added unit tests
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
closes https://github.com/SigNoz/keystone-pod/issues/2
<!--Please delete paragraphs that you did not use before submitting.-->
#### Description
Extends the metrics heatmap to logs, traces and audit logs, so a heatmap
can be built ad hoc from raw events with any filter instead of from a
pre-generated metric.
- The aggregation is the bare field to bucket, e.g. `duration_nano`.
Each cell counts events. Any other aggregation, such as `count()`, is
rejected, and a query takes one aggregation.
- Rows without the field are excluded by default: `<field> EXISTS` is
ANDed onto the filter, so it also applies to the resource filter and the
group limit. A present but non-numeric value counts as 0.
- `bucketOptions` is optional and defaults to the log axis, as for
metrics. Distribution requests work the same way.
- The heatmap is the time series query with the value bucket joining the
group keys, so a row is one cell and the response shape is unchanged.
Results are cached, and a cached axis is trimmed to the requested
window.
- The bucket expressions move to `pkg/querybuilder/heatmap.go`, shared
with metrics. The value is read once through a `WITH` alias instead of
being repeated in every bound. The metrics SQL is unchanged.
- A disabled query in a heatmap request feeds a formula, so it builds as
a time series, as metrics already did.
- A field nothing carries draws an empty chart with the key-not-found
warning.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/338
#### Additional Information
- No API schema change.
- Integration tests (`querierlogs/17_heatmap.py`,
`queriertraces/16_heatmap.py`, `auditquerier/02_heatmap.py`) cover
linear, log and default axes, group by, rows without the field, and the
rejections.
- Backend only. Dashboards first, explorers later.
#### Description
- A logs or traces time series query with a group by and a limit ranks
its groups over the statement window. On a partial cache hit the gap
statement ranked over the gap, so the response was a union of the
winners of each piece, each with points for part of the window. A
narrower window served from a wider cached entry got the wider window's
winners.
- No piece of a window can stand in for the window, so such a query is
no longer served from the cache. Its fingerprint is empty, the same way
an AI builder query opts out today. Metrics are not affected, their
limit is applied after the full series set is read.
- Integration tests: a top-2 over a window whose first half is cached
must return the window's two winners with points for the whole window,
and a top-1 over the second half of a cached window must return that
half's winner. Both match the request with noCache.
#### Issues closed by this PR
ClosesSigNoz/nerve-pod#627
#### Additional Information
- The first commit adds the tests alone. CI ran them against main and
both failed. The second commit is the fix.
- This is the stop gap. Caching these queries exactly needs the ranking
summary of each piece, top-K totals with a threshold, so the merge can
rank with bounds and fall back when it cannot prove the top-N. That is a
design item, not a cache patch.
#### Description
- A timeShift query's window is moved into the shifted clock when the
query is built. On a partial cache hit the missing range comes from that
window, and the ranged gap query shifted it again, so the gap was read
one shift too far back, and the wrong points were served and written
back to the cache.
- The ranged query now takes the range as given. The spec copy already
carries the shift, so the gap statement reads the right data and the
postprocess shift moves it forward once.
- Integration test: a timeShift(3600) logs query with its first 30
minutes cached must return the same points for the next 20 minutes as
the request with noCache.
#### Issues closed by this PR
ClosesSigNoz/nerve-pod#625
#### Additional Information
- The first commit adds the test alone. CI ran it against main and it
failed with only that test failing in the suite. The second commit is
the fix.
#### Description
The querier computes a formula in Go after its queries return, so a
formula having cannot run as SQL HAVING. It was accepted and ignored.
The having now filters the formula values in Go. It uses the having
grammar, validation, and messages of a builder query, with the formula
name and __result as the only references. A time series keeps the
points that match and drops a series with no points left. A scalar
result keeps the rows that match. The having runs before functions,
order, and limit, as HAVING does in SQL. A bad having fails the
request before any query runs.
#### Issues closed by this PR
ClosesSigNoz/nerve-pod#612
#### Additional Information
Assisted-by: Claude Opus 5.5
#### Description
- A negative formula `limit` now returns 400, as for builder queries.
Before, validation accepted it, and the series limit treated it as no
limit.
- An empty order key on a `raw`, `raw_stream`, or `trace` request now
returns 400. Before, validation accepted it. Aggregation requests keep
their own order key check, which lists the valid keys.
- A missing `requestType` returned `invalid request type: {}` and
suggested `timeseries`, which the decoder rejects. It now returns
`requestType is required`, with the valid request types as suggestions.
The decoder error for an unknown value lists the same set.
- Validation now accepts exactly what `RequestType.Enum` lists. The
spec, the decoder error, and validation read one list, so they cannot
drift apart.
#### Issues closed by this PR
ClosesSigNoz/nerve-pod#609
#### Additional Information
- Existing alerts should not be affected. Stored rules load without
validation, and the querier validates only in the `query_range` handler.
The UI does not send a negative formula limit or an empty order key.
- Part of https://github.com/SigNoz/nerve-pod/issues/428
#### Description
- The query_range examples cover signals and request types but none of
the functions a builder query can carry, so a client has no place to
learn the argument shapes.
- Adds one example per function family: timeShift, fillZero, the four
threshold functions, runningDiff, ewma, median, cumulativeSum, log,
anomaly. Each description says what the function does to the series,
which arguments it takes, their units and their defaults.
- A test unmarshals every example into a `QueryRangeRequest` and
validates it, so an example cannot drift from the request type.
- The OpenAPI spec is regenerated in its own commit.
#### Description
- Saving a rule, and cloning or renaming one from its details page, now
refreshes the v3 rules list instead of v2. The list page (#13322) and
`useListAllRules` (#13440) both read v3, so nothing reads the v2 list
anymore.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/718
#### Description
- The AI assistant's Alerts context picker now loads rules with one
`/api/v3/rules` request (`limit: 200`, the most the API returns per
call) instead of the deprecated `GET /api/v2/rules`. The Dashboards tab
next to it already loads one page (`limit: 1000`) the same way.
- Alert context chips read their names from the same cache entry.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/717
#### Description
- The planned downtime rule picker now reads `/api/v3/rules` through a
new `useListAllRules` hook instead of the deprecated `GET
/api/v2/rules`.
- The picker lists every rule, and v3 returns at most 200 per call, so
the hook fetches the remaining pages in parallel, like
`useChannelOptions` does for channels. Orgs with 200 rules or fewer make
one request.
- The planned downtime story now mocks `/api/v3/rules`.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/713
#### Description
- `GET /api/v2/rules` is deprecated in favor of the paginated
`/api/v3/rules`, and #13158 removes it. This moves the home page alerts
widget to v3 ahead of that.
- The widget still shows firing rules first, then the most recently
updated others, up to 5. It asks v3 for the firing rules first and
fetches the latest rules only when fewer than 5 are firing, instead of
loading every rule.
- Clicking a rule now opens the overview with only `ruleId`, like the
alert list, triggered alerts and Metrics Explorer. The overview loads
the rule and builds its query from it, so the `compositeQuery` the old
link carried is not needed.
- The home page story now mocks `/api/v3/rules`.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/712
#### Description
- `@SigNoz/pulse-frontend` now owns `components/ListFilter`,
`components/ListViews` and `hooks/listViews`. Only the dashboards list
and the alert rules list use them.
- It also owns `container/PlannedDowntime`, `hooks/alerts` and the home
page alert rules widget (`container/Home/AlertRules`).
- Until now these fell to the `/frontend/` catch-all, so changes to them
also needed `@SigNoz/frontend-maintainers`.
<!--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
- bump go version in ci to 1.25
---------
Co-authored-by: Ashwin Bhatkal <ashwin96@gmail.com>
#### Description
Stacked on #13287, so that one merges first.
The saved views tests touch about 98% of the lines, yet a lot of
behaviour could break without any of them failing. This adds tests for
that behaviour:
- The row link and Copy link follow the selected time range.
- Saving from the header: a failed update, blank names, pressing Enter
twice, keeping the view color, and saving the query that was run.
- The hover card while it loads, when it fails, and when it closes.
- Restoring the last used view, per source.
- The unsaved changes check and spec conversion against what the server
stores.
It also adds the first saved views e2e specs, run against a real
backend:
- Open, create, update, discard, delete and rename a view on the logs
explorer, and check the header is still clean after a reload.
- Row links and Copy link carry the time range, and a Ctrl/Cmd or middle
click opens the view in a new tab.
While adding the tests I found that renaming a view from the row menu
sent the list's copy of the view back as a full replacement, so a stale
list could undo someone else's edit. Rename now reads the stored view
first, and falls back to the list's copy when the user cannot read the
view.
#### Issues closed by this PR
Closes https://github.com/SigNoz/uforge-pod/issues/6397
#### Additional Information
- Each new test fails when the code it covers is broken on purpose.
- Jest: `pnpm jest src/container/SavedViews
src/utils/__tests__/buildExplorerNavigationUrl.test.ts`
- E2E, with the stack up (`pnpm env:start` in `tests/e2e`): `pnpm
playwright test tests/saved-views --project=chromium`
- Opening a view the user cannot read still sends the GET, from
`usePreferenceSync`, which has no read check. The server answers 403 and
the header shows "No view access".
## Description
- TestAggrGroup failed four times this week in CI with "received batch
too early after 299.5ms" and "Expected aggregation group to be empty
after resolving alerts".
- The test timed batches with its own clock. The group resets its timer
before it calls notify, so two ticks can be under one group interval on
the test's clock by a millisecond. The test now reads the tick time the
group puts in the context, which is the clock the group itself uses.
- The empty check ran as soon as the batch arrived, but the group
deletes resolved alerts after notify returns. The test now waits for the
delete.
- Checks inside the notify callback use assert, not require, because the
callback runs on the group's goroutine.
## Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5121
Closes https://github.com/SigNoz/engineering-pod/issues/5636🤖 Generated with [Claude Code](https://claude.com/claude-code)
#### Description
`ResolveColumn` dropped the not-found warning from `Resolve`, so a group
by, select field, order by, or aggregation on an unknown key returned
data with no warning. The le bucket key the histogram rewrite adds does
not warn, and meter queries still report no warnings.
#### Issues closed by this PR
ClosesSigNoz/nerve-pod#600
#### Additional Information
Assisted-by: Claude Opus 5.5
#### Description
- Gracefully handle aggregation keys names
- otherwise `sum(http.response.body-size)` became a subtraction
- `count_distinct(k8s.pod.labels.app.kubernetes.io/name)` a division,
and both returned null or 0 with no warning to agent.
- Parsing failure, a wrong argument count, or an argument that is not a
field name now returns 400, earlier 500.
- Each argument of an aggregation call now comes from the source text
before the SQL parse. Predicates go to the filter grammar. A value
argument must be one filter-grammar key, so keys with `-`, `/` and
`:type` resolve.
- rewriter parsed the whole expression with the chsql parser before the
filter grammar saw the predicate. Filter-only syntax in an `*If`
predicate (`EXISTS`, `NOT EXISTS`, `CONTAINS`, `NOT CONTAINS`) failed in
that parser and returned 500.
#### Issues closed by this PR
ClosesSigNoz/nerve-pod#599
#### Description
- A `body.` key that metadata does not report failed with 404 "field not
found" in a select, group by, order by, or aggregation. The logs
fallback kept it with no data type, and the JSON body read needs one. A
filter on the same key worked, because the operand types it.
- The fallback now types it as `bodyPath` does for a bare key: a string
path when no operand types it. Filter SQL does not change.
- `TestResolveColumn_UnknownKey` resolves an unknown key in a column for
every storage and field context and allows only invalid input as an
error. On main, only the logs JSON body `body` context failed.
- The body path parity integration test now covers the `body.` spelling,
and group by, `count_distinct`, and select.
#### Issues closed by this PR
Closes SigNoz/nerve-pod#610
#### Additional Information
- The not-found warning for group by and select reaches the response
with #13421. #13421 also changes `ResolveColumn` to return warnings; the
second PR to merge updates the call in `resolve_test.go`.
#### Description
- Adds a host-agnostic `qb` driver
(`tests/e2e/drivers/query-builder.ts`) and ~110 query builder tests
across the logs, traces and metrics explorers, the V2 panel editor and
alerts. Assertions read the outgoing `query_range` body and the URL
query, never exact counts.
- Covers URL handling (malformed, partial, legacy shapes,
back/forward/reload), the filter editor (suggestions, quoting, IN lists,
nesting, validation, keyboard, recents, quick filters), metric-type
defaults, aggregations, add-ons per panel kind, multi-query and
formulas, trace operator, signal and query-type switching, cross-host
round trips (explorer → dashboard, alert save → reopen) and
run/cancel/error states.
- Each regression-driven case names the fix it guards (#13257, #12705,
#12496, #10048, #9947, #9732, #11208, #9637, #11035, #10100, #12755,
#9066, #8250).
- Seeds a gauge and a histogram per worker (golden metrics are all
sums); exports the seeder's retrying POST for that.
- Shared helper fixes found under load: `pickChannelByName` re-clicks
while the select is still closed; `holdQueryRange` tolerates a request
cancelled while held.
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#662
#### Additional Information
- Stacked: #13315 (legacy having and limit fixes) → #13323 (added query
keeps its signal) → this PR. Merge in that order.
- Regression tests for both fixes (legacy having `COUNT() > 5`, negative
limit, added query's signal) fail against the unfixed app.
- Stable across 2 consecutive CI-mode runs on chromium (198 passed, 0
flaky). Meter source switching is not covered yet: the stack has no
meter metrics.
#### Description
- Alert rules list now uses the server-side `/api/v3/rules` endpoint.
Filtering, sorting and pagination happen on the backend instead of the
client.
- The search bar takes the filter DSL (`name`, `severity`, `alert_type`,
`rule_type`, `created_by`, `labels.<key>`, …) with autocomplete. Invalid
queries show the backend's error inline.
- New saved views rail: built-in views (My alert rules, All, Firing,
Disabled) plus org-shared custom views backed by `/api/v2/rule_views`.
- Search bar, filter zone and views rail come from the shared
`components/ListFilter` and `components/ListViews`.
#### Issues closed by this PR
Closes: https://github.com/SigNoz/pulse-pod/issues/594
#### Screenshots / Screen Recordings
[Visual diff](https://claude.ai/artifact/944j4x95qf1Ybh1pEw8uNt) against
#13372, dark and light: 138 pairs, 86 changed, 22 new, 2 removed, 28
identical. The `alert-rules` tag holds this PR's changes; Dashboards
List pairs are within the measured noise floor.
#### Additional Information
- Depends on #13372; review after it merges.
#### Description
- The Jira notification channel so far worked with Jira Cloud only. This
PR adds support for self-hosted Jira (Server / Data Center, version 8.14
or newer).
- The channel form now asks where Jira runs: Cloud keeps email + API
token, self-hosted takes a personal access token.
- Behind the scenes the channel talks to self-hosted Jira the way it
expects: token as a Bearer header, the v2 REST API, and wiki-markup text
for the issue description and comments. The issue still carries the
status panel, alert details and links.
- Existing Jira Cloud channels keep working unchanged; a migration marks
them as Cloud.
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#691
#### Screenshots / Screen Recordings
Self-hosted jira channel form:
<img width="1512" height="828" alt="Screenshot 2026-11-05 at 2 27 26 AM"
src="https://github.com/user-attachments/assets/ad990a40-d469-4c16-aed0-cd897f441f2f"
/>
Jira cloud channel form:
<img width="1512" height="828" alt="Screenshot 2026-11-05 at 2 25 59 AM"
src="https://github.com/user-attachments/assets/42b1c7fa-f692-438f-8892-87a92d6ef1a5"
/>
#### Additional Information
- Design doc: https://app.notion.com/p/40efcc6bcd1981f98d60cdaaec074d94
(discussion SigNoz/pulse-pod#690, docs follow-up SigNoz/pulse-pod#692).
- Docs PR on signoz.io comes separately.
- Breaking change on the v2 Jira spec: `deployment` is now required and
the credentials sit under `auth` (`{email, apiToken}` for cloud, `{pat}`
for self-hosted). Migration 144 restructures stored channels.
---------
Co-authored-by: Ashwin Bhatkal <ashwin96@gmail.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
- `offset` was only applied to raw and trace requests; scalar,
time_series, trace operator and formula results now honor it too.
- Reworded the `limit`, `offset` and `cursor` descriptions to say which
request types use them.
<!--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/677
#### Description
- e2eci now runs on Node 22, like jsci, goci, the gor workflows,
`frontend/.nvmrc` and the with-web image. It was on `lts/*`, which moved
to Node 26 in late October.
- Under Node 26, Playwright 1.57 never finishes writing a trace, so
every first retry hangs until the 60 s test timeout (found in
[#13210](https://github.com/SigNoz/signoz/pull/13210)).
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/688
#### Description
- e2e no longer runs when the only frontend changes in a PR are under
`frontend/src/api/generated/`.
- `pnpm generate:api` rewrites these files whenever the backend API spec
changes, so most backend PRs touch them with no frontend change.
Backend-only PRs already skip e2e.
- The jsci type check still catches a regenerated client that breaks a
call site. Add the `safe-to-e2e` label to force a run.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/701
<!--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
Distribution queries are basically each row of a heatmap summed up to
counts, and rotated to have the heatmap Y axis as the distribution
graph's X axis.
Query range API supports distribution queries in exactly this way by
going through the usual query range flow for heatmaps, and then summing
up the counts for each bucket.
Because this is no longer time series data, a new response type is
added.
<!--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/675
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
All existing integration tests for heatmaps have been extended to test
for distributions
<!--Please delete paragraphs that you did not use before submitting.-->
#### Description
- Move the search bar, filter zone, views rail, command header and
status bar out of the dashboards list into shared
`components/ListFilter` and `components/ListViews`, with `useListViews`
for view state, so the alert rules list can reuse them.
- Dashboards behaviour is unchanged, except: the search icon and Run
query stay centred when a query wraps, selected Created-by options are
no longer bold, and the search box is 1px shorter (whole-pixel line
height).
- Dashboards list stories gain `LongQuery` and `CreatedByFilterOpen`;
their plays move to `dashboardsList.play.ts`.
- Add unit tests for the filter draft and the views rail.
#### Screenshots / Screen Recordings
[Visual diff](https://claude.ai/artifact/944j4x95qf1Ybh1pEw8uNt) against
`main`, dark and light: 116 pairs, 71 changed, 4 new, 41 identical. The
`fix` tag holds the two new states; `shift` is the 1px search box change
plus timestamp noise.
#### Additional Information
- Alert rules adopt these in #13322.
<!--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
- AI explorer list view gets the column picker (`useOptionsMenu`, keys
from `/api/v1/fields/keys`), like the trace view. Picked columns are
sent with the list query, so adding or removing one refetches;
reordering does not.
- The list panel keeps its own columns in the AI preference slice, so
saved views and `options.selectColumns` in the URL cover it too.
- The list panel defaults to the current static columns. The column
store keeps only widths.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
https://github.com/SigNoz/nerve-pod/issues/551
<!--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/f2d455f5-ba97-4325-8832-79ac6b637cf7
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- Stacked on #13297.
- Review commit by commit: preferences → list view. Each commit builds.
- Column order picked on the list view before this PR was kept in the
table's column store; it is ignored now, so those users see the default
order once. Column widths are kept.
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- `traces-funnels/steps.spec.ts` TC-04 failed with a strict-mode
violation on `funnel-step-action-delete`: step 1's popover was still
fading out when step 3's opened, so two delete buttons were briefly
visible.
- The spec now waits for the first popover to close. Verified locally:
0/5 before, 5/5 after.
#### Description
- A row click in the traces List view now opens the span drawer. We need
to know how users still reach the trace details page.
- The explorer now logs `Traces Explorer: Open trace clicked` on each
click that opens the trace details page.
- The event has two attributes:
- `panelType`: `list` or `trace`. These are the same values as `Traces
Explorer: Data present`.
- `source`: `row`, `row_icon`, `trace_id` or `span_drawer`.
#### Issues closed by this PR
Part of https://github.com/SigNoz/events-pod/issues/195
#### Additional Information
- Stacked on #13411.
- The event does not fire when a user opens the trace_id link from the
right-click menu. The browser does not tell the page which menu item the
user picks.
- Entry points outside the traces explorer do not log this event.
<!--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
- Adds saved views to the AI observability explorer.
- New `ai_observability` preference slice. Columns are stored per panel
in localStorage (`AI_OBSERVABILITY_OPTIONS`); the slice holds only the
open panel's `{ columns, panelType }`. Only the Traces tab keeps columns
today.
- The Traces tab reads columns through `useOptionsMenu`, like the logs
and traces lists. Defaults are a fixed list
(`TRACE_VIEW_DEFAULT_COLUMNS`) matching the backend's trace aggregates,
so the table no longer waits for the keys API.
- The URL carries the open panel's columns in `options.selectColumns`;
logs and traces ignore it on the AI explorer route.
- Fixes saved views opening on the wrong tab: the explorer now reads the
tab from the URL For AI View
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
<!--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/daab1db3-b8df-435a-8252-d6bb35a86e99
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- Review commit by commit: preferences → saved views → trace view →
explorer wiring → tab fix → discard and loading fixes. Each commit
builds.
- Columns picked on the Traces tab before this PR were kept in the
table's column store; they are ignored now, so those users see the
defaults once. Column widths are kept.
- The AI Explorer story is not updated in this PR.
- SB Shot -
https://claude.ai/artifact/R77CFZKS71pSxNLV4ZhpxY?sk=dM6jybUP456zVTcW9xrz8Q
- Check to have a summary of comment raised and closed with greptile
https://github.com/SigNoz/signoz/pull/13297#issuecomment-6508458226
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- Move `/api/v2/rules/{id}/history/*` routes from role gates to
`CheckResources`.
- `stats` and `overall_status` check `rule:read` on the id.
- `timeline` and `top_contributors` check `rule:read` on the id plus
read on the telemetry resources the rule's query touches, so a broad
telemetry grant alone cannot read another rule's firing history.
- `filter_keys` and `filter_values` check `rule:read` on the id plus
list and read on the field resources of the signals the rule queries.
The field kinds come from the same query walker `query_range` uses,
mapped telemetry kind to field kind.
- Integration tests cover each gate with and without the grant. No
migration, rule tuples come from #13251.
#### Issues closed by this PR
Part of SigNoz/pulse-pod#286
Part of SigNoz/pulse-pod#355
#### Additional Information
Fourth of five stacked PRs splitting #12927. Stacked on #13251.
- Five ClickHouse errors that user SQL causes returned 500. They now
return 400 with a suggestion, as #13328 does for 170, 288, and 407: 386
(no common type), 215 (column neither in GROUP BY nor in an aggregate),
184 (aggregate where none is allowed, mostly an alias that shadows a
column in WHERE), 190 (arrays of different sizes), 427 (a regular
expression RE2 cannot compile).
- Code 48 stays a 500. A correlated subquery over a distributed table
raises it, but the code covers every unimplemented feature, so a mapping
could hide a fault in generated SQL.
- 215 and 386 can also come from a builder bug; that is the trade-off
#13328 made.
- `test_clickhouse_user_mistake_is_bad_request` raises each code with a
real query, plus a builder `REGEXP` filter.
#### Description
Groups small pie slices into one automatic **Other** slice, so a pie
with many groups stays readable.
- A slice under 1% of the total joins Other, and the pie draws at most
15 slices; Other needs at least 2 members and is always the last arc.
- Automatic, with no panel setting.
- Legend lists every series; hiding or showing one regroups Other from
the series left showing.
- Hovering Other lists its members in the timeseries-style tooltip;
clicking pins it, and clicking a member drills into that series through
the existing slice drilldown.
- Storybook: `Pages/Dashboards/Detail/Panels/PieOther`.
https://github.com/user-attachments/assets/143626a2-7e8a-48ac-a9cd-742f0fcd18ec
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/363
#### Additional Information
- V2 dashboards only; V1 pie is unchanged.
#### Description
- Drivers now scroll with the mouse wheel through a new `wheelTo` helper
(`drivers/scroll.ts`) instead of `scrollIntoView` /
`scrollIntoViewIfNeeded`. Scripted scrolling moves even an `overflow:
hidden` box a user can't scroll, so the channel form clipping passed e2e
while users couldn't reach the fields.
- `wheelTo` wheels over the target's visible scroll container until the
target is centred and not covered by a sticky header, then asserts
`toBeInViewport()`. If a turn doesn't move the target, it fails with "a
container clips it instead of scrolling".
- oxlint now rejects `scrollIntoView` and `scrollIntoViewIfNeeded` in
`tests/e2e`, and `AGENTS.md` adds the rule.
- The panel handle's `scrollIntoView()` is renamed to `wheelIntoView()`.
- The channel form (`fillField`) and the alert rule form (threshold
channels, labels, advanced options, evaluation settings) wheel to lower
controls before acting, so a clipped form fails the existing specs, not
only the dedicated regression spec.
#### Additional Information
- Other `click()` / `fill()` calls still use Playwright's scripted
scroll; the wheel is applied where content can sit below the fold.
- Verified on Chromium only.
Closes: https://github.com/SigNoz/platform-pod/issues/3338
#### Description
- The e2e suite now runs on 10 shards instead of 4. Since
[#13189](https://github.com/SigNoz/signoz/pull/13189) the suite is about
twice as long, so each of the 4 shards spends 16 to 20 min in tests and
a PR waits 23 to 27 min for e2e.
- Each shard also pays about 7 min of fixed setup, so 10 shards should
bring the slowest one to about 16 to 17 min. Past 10, every 2 extra
shards save only about 1.5 min.
#### Description
- The traces and logs explorers now look the same above and around the
table. Both toolbars use the same row height, spacing and controls.
- The traces Options control is now an icon button, the same as the logs
one.
- The logs Options menu now uses the design system buttons and number
input. Its rows no longer fill on hover. The logs Options button no
longer turns blue on hover.
- Max lines per row now clamps a typed value to 1–10. Before, it ignored
1 and accepted 2.5.
- The logs list now reaches the bottom of the page.
- The logs list and table now use Inter, the same as traces. Only the
raw view keeps the mono font.
- The traces and logs table rows are now 30px tall, the row height in
the design.
#### Issues closed by this PR
Part of https://github.com/SigNoz/events-pod/issues/195
#### Screenshots
https://claude.ai/artifact/6zsvLZFYYzfFrPy9r6Xq9F?sk=ymS3KLyoZjWMf2axjY1-ng
#### Additional Information
- Stacked on #13392.
- The traces styles moved to CSS modules. The LLM Observability explorer
renders the same global classes, so global rules from one page could
restyle the other.
#### Description
- Configuration sidebar items (Notification Channels, Routing Policies,
Planned Downtime) no longer turn bold when selected; only the colour
changes, so the label no longer shifts.
Closes: https://github.com/SigNoz/pulse-pod/issues/693
#### Description
- List view now loads spans on scroll, 100 at a time. It stops once a
page comes back short or the query limit is hit. Previous / Next
controls are removed from the list.
- Traces tab now uses the table's own pager. Page and page size are kept
in the URL. Default is 20 traces per page instead of 10 so the table
fills the space. API does not send a total, so the pager offers the next
page only after a full page.
- New query clears the old rows and shows the loader. It also resets the
traces tab to page 1 and closes the span drawer.
- Span drawer is now on for everyone. Removed the
`TRACES_SPAN_DRAWER_ENABLED` flag.
- Added a hover icon at the start of each row to open the trace in a new
tab. If the API returns the same span twice, only the hovered row shows
the icon.
- Table ui: list and traces tables run edge to edge, empty cells show a
dash in the border colour, rows use the table default height and the
hover / active colours from design.
#### Issues closed by this PR
Part of https://github.com/SigNoz/events-pod/issues/195
#### Screen recording
https://github.com/user-attachments/assets/4f7897bd-0c6b-4ecf-aaf2-231834082547https://claude.ai/artifact/LmXzEheZYZ1xdpshNjGBRf?sk=dOC3fM6uD0n_fKnzLAnZXQ
#### Additional Information
- If the data ends exactly on a full page, Next opens an empty page and
the pager steps back to the last one.
- A failed page request hides the pager. Same as other explorers, so
keeping it as is for now.
- Icon sits in a left gutter as discussed with design.
#### Description
- Alert Details crashed to the error boundary when the URL
`relativeTime` couldn't be parsed (e.g. `?relativeTime=1y`). It now
falls back to the default 30m range.
- The "triggers in …" and stats card labels show the range actually
queried, not the raw param.
- `relativeTime=3months` (accepted by validation since #12402) silently
resolved to 30 minutes. Multi-month values now resolve to 30 days per
month.
- Moved `useRouteTabUtils` into its own file to break the `hooks.tsx` ↔
`container/AlertHistory` import cycle.
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#674
#### Additional Information
- Repro: open `/alerts/overview?ruleId=<id>&relativeTime=1y` (crashes on
`main`), or `relativeTime=3months` (shows 30m of history).
- No story change. The fixed page renders the same as the existing
AlertOverview story.
#### Description
Greptile keeps flagging the same things on frontend PRs, and authors
keep fixing them by hand. I read every Greptile thread on frontend PRs
from 2026-09-16 to 2026-10-30 (50 PRs, 129 threads) and checked each one
against the final head of its PR, to find out why. This PR fixes the
causes, not the findings.
What was going wrong:
1. The agents that write our code never saw the testing rules. They
lived only in `frontend/.cursor/rules/*.mdc`. Claude Code loads
`CLAUDE.md` and `.claude/rules/`, Codex loads `AGENTS.md`, and neither
pointed at the `.mdc` files. Greptile reads all of them. So 60 of the 76
threads that cite a rule came from rules the authoring agent was never
given: untyped `jest.fn()`, imports from `@testing-library/react`,
`fireEvent`, too many tests per file.
2. With no rule in sight, the agent copies the test next to it, and most
of those break the rules. Of 914 test files on `main`, 41% import from
`@testing-library/react` and 54% have an untyped `jest.fn()`.
3. Two files disagreed. `frontend/AGENTS.md` said to find elements by
test ID; `testing-conventions.mdc` said `getByRole` first and test ID as
a last resort. Greptile enforced whichever it read.
4. Some rules were too broad. When an author replied with a reason,
Greptile conceded 8 of 9 times: deleting a comment whose code was
removed, a reason next to a `pnpm-workspace.yaml` exclusion, the
AI-owned banner the `signoz-page-story` skill requires, an
`onX={jest.fn()}` placeholder that is never asserted, an antd button
added beside antd tabs, and "3 to 5 tests per file", which half of
`main` breaks.
5. The test harness pushed people off it. `customRender` used `wrapper:
() => <AllTheProviders>{ui}</AllTheProviders>` and ignored `children`,
so `rerender(newUi)` kept rendering the first element. Authors imported
`@testing-library/react` directly to get a working `rerender`.
6. Most logic findings fell into a few shapes no rule named: a test that
passes without the change, a story play that fails on a valid control
value, a stacked branch broken by a rename in the branch below, a URL
rewrite that drops params it doesn't own, a query key missing a request
input, an identity key built from joined display text.
7. Review fixes landed by force push: 60 of 139 force pushes were
amends, although `pull-requests.md` forbids amending pushed commits.
After each one Greptile re-posted findings it had already made. 34
threads that kept their code were resolved with no reply, so Greptile
never learned why.
What this PR changes:
- `tests/test-utils` renders `children`, so `rerender` works. Three
`withAuthZ` "does not re-render" tests passed only because of that bug:
`rerender` never re-rendered, so the render count could not change. They
now record the router context each `checks` call receives and assert it
is the same object after a rerender with equal params. They fail on the
old harness and with an unstable router context.
- `frontend/.cursor` is gone. Its content moved to files every agent
loads:
- `.claude/rules/frontend-tests.md`, scoped to `*.test.ts(x)`: the three
testing `.mdc` files in one page. Test ID first, the `jest.fn()`
placeholder exemption, no per-file test limit, and "a test fails without
its change".
- `.claude/rules/frontend-state.md`, scoped to `src/**/*.ts(x)`:
`state-management.mdc` plus the URL, query key and form state lessons
from point 6.
- The `migrate-state-management` skill moved to
`frontend/.claude/skills`.
- The `@signozhq/ui` rule from `ui-components-and-icons.mdc` moved to
`frontend/AGENTS.md`, with the antd-sibling exception.
- `frontend/AGENTS.md` lists which rule file applies to which path, for
agents that don't load `.claude/rules/` on their own.
- New lessons from the logic findings:
- `frontend/AGENTS.md`: identity keys come from structured values; a
payload saved under another kind keeps only the fields that kind
accepts; a flow that gains a request gates on that request's permission.
Verification adds "revert the change and watch the test fail" and
"type-check every branch above you on a stack".
- `storybook.md` and the `signoz-page-story` skill: plays live in
`__story_mockdata__/<page>.play.ts`, pass for every control value, and
mocks answer every request a reachable state makes.
- `comments.md`: update or delete a comment together with its code;
config entries and skill banners keep their reason.
- `pull-requests.md`: review fixes are new commits and `main` comes in
by merge (stacks excepted); every thread gets the fixing commit or a
reason before it is resolved.
- `src/tests/README.md` and `src/storybook/README.md` now match the
rules.
What we want a PR to look like after this. A test:
```tsx
// before: raw import, untyped mock, role query, fireEvent, asserts only that something exists
import { render, screen, fireEvent } from '@testing-library/react';
const onSave = jest.fn();
fireEvent.click(screen.getByRole('button', { name: /save/i }));
expect(screen.getByText(/saved/i)).toBeInTheDocument();
// after: harness import, typed mock, test ID, userEvent, asserts the payload
import { render, screen, userEvent } from 'tests/test-utils';
const onSave = jest.fn() as jest.MockedFunction<(view: SavedView) => void>;
const user = userEvent.setup({ pointerEventsCheck: 0 });
await user.click(screen.getByTestId('save-view'));
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ name: 'errors' }));
```
A story whose file reads as a list of states:
```tsx
// stories/__story_mockdata__/<page>.play.ts holds the clicks
export async function openEditDrawer({ canvasElement, args }) { ... }
// <Page>.stories.tsx only names it
export const EditDrawer: Story = { play: openEditDrawer };
```
A review thread, answered and then resolved:
```
Fixed in 4df6f3198d: the row link now carries the current time range.
```
or, when the code stays:
```
Keeping this: the button sits in an antd tab strip, a @signozhq/ui button would not match its siblings until the strip migrates.
```
#### Additional Information
- Only one code change: `tests/test-utils.tsx` plus the three
`withAuthZ` tests. Every jest suite that calls `rerender` passes with
the fixed wrapper (61 suites, 735 tests). To check the harness fix:
`pnpm jest src/lib/authz/components/withAuthZ`, then revert
`tests/test-utils.tsx` and run it again: the three router context tests
fail.
- About 80 existing stories still hold inline plays. They move the first
time their story file is edited; there is no sweep in this PR.
- Open for review: the antd-sibling exception in `AGENTS.md`, whether
`frontend-state.md` should apply to every `src` file, and merge instead
of rebase for bringing in `main`.
#### Description
- Infrastructure Monitoring sent two analytics events for most actions:
an old `Infra Monitoring: …` event and a newer `infra_*` one. The old
duplicates are gone. The newer events now follow the event naming
convention: `Infra Monitoring: <Action>` names with camelCase
attributes.
- Each action now sends one event:
- List opened or category switched: `Infra Monitoring: List viewed`, was
`infra_list_viewed`. Replaces `page visited` and the category switch
`filter applied`. The old `total` row count has no equivalent.
- Drawer opened or tab changed: `Infra Monitoring: Drawer tab viewed`,
was `infra_drawer_tab_viewed`. Replaces the drawer `page visited` and
`tab changed`.
- List search: `Infra Monitoring: Filter customized`, was
`infra_filter_customized`. Replaces `filter applied`.
- Drawer logs, traces and events filter: `Infra Monitoring: Drawer
filter customized`, was `infra_drawer_filter_customized`. Replaces
`filter applied`.
- Drawer time range: `Infra Monitoring: Drawer time range customized`,
was `infra_drawer_time_range_customized`. Replaces `time updated`.
- Group by: `Infra Monitoring: Group by customized`, was
`infra_group_by_customized`. Replaces `group by changed`.
- Explorer link: `Infra Monitoring: Explorer navigated`, was
`infra_explorer_navigated`. Replaces `explore clicked`.
- List row click: `Infra Monitoring: List row clicked`, new, with
`openedInNewTab` and `isGrouped`. Replaces `item clicked`.
- Drawer trace row click: `Infra Monitoring: Drawer trace clicked`, new.
Replaces `item clicked`.
- List time range, column settings and column sort: `Infra Monitoring:
Time range customized`, `Column customized` and `Column sorted`, renamed
from `infra_*`. They had no old duplicate.
- Every event carries `entityType`, so host rows report `hosts` where
`item clicked` reported `k8s`.
- `Infra Monitoring: Get Early Access Clicked` is now `Infra Monitoring:
Get early access clicked`, with `entityType` instead of `entity_type`.
- Removed the `eventEntity` and `eventCategory` props and the
`InfraMonitoringEvents` enum, which only fed the old events.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/356
#### Description
- On `use_json_body` tenants the log body is an object. Each view had
its own way to read it, so the Default view crashed on object fields,
Column and Raw printed `[object Object]`, and Live logs, the dashboard
List panel and the pod Events tab showed the raw `{"message":…}`
wrapper.
- Added `lib/logs/logBody.ts` as the one place that turns a body into
display text. Every view, the drawer header, the List panel and k8s
Events read the body through it. A missing body renders as an empty
string.
- The `{message}`-only collapse moved out of the V5 converter into this
module. The converter still calls it, so the Explorer behaves the same.
- Selected fields with object values render as JSON in all three
Explorer views. They are not offered as add-to-query filters, same as
the drawer.
- Added `useIsBodyJsonEnabled` for the feature flag lookup. Stories
cover each surface with JSON bodies.
#### Issues closed by this PR
Closes https://github.com/SigNoz/events-pod/issues/104
#### Screenshots / Screen Recordings
Full before/after diff of every logs, list panel and pod events story:
https://claude.ai/artifact/5Xeyedu4HNpiaEk7DF6C9c
#### Additional Information
- Touches `ListLogView`, `RawLogView` and the table columns hook, which
Pipelines, Hosts and the other Kubernetes entities also render through.
The visual diff shows those screens pixel-identical.
- `.claude/rules/log-body.md` documents the access pattern, so a review
flags a direct body read. A planned follow-up that made `ILog.body` an
opaque type was dropped: a cast or `any` walks past the brand, so it
added type ceremony without a real guarantee.
- Add-to-query from the Default view keeps a number or boolean value
typed, as the drawer does.
- Quick filters on `body.*` keys are not covered here.
#### Description
- Fixes the Vanta findings for dompurify (GHSA-cg2g-xh64-v4q7), sharp
(GHSA-wq5f-xc86-pv6w) and source-map-js (CVE-2026-93749).
- dompurify → 3.4.17 and sharp → 0.35.5. The dompurify override floor is
raised so the copies pinned by `@grafana/data` and `monaco-editor` move
too.
- source-map-js → 1.2.2 by bumping `postcss` to 8.5.32 and
`typescript-plugin-css-modules` to 5.3.0, which now require it. `sass`
and `css-tree` accept any 1.x and share that copy.
- Those bumps also floor nanoid `^3.3.20` and less `^4.9.1`, so both
overrides are removed. Resolved versions are unchanged.
#### Issues closed by this PR
https://github.com/orgs/SigNoz/projects/59/views/18?pane=issue&itemId=293486763&issue=SigNoz%7Cnerve-pod%7C589
#### Screenshots / Screen Recordings
sbshot: https://claude.ai/artifact/JZ4gauNPS8CTnMu7aXGUeT
- 129 stories (first of every title plus log-body states), dark and
light: 237 of 258 pairs identical, 21 moved from clock text, focus
rings, scroll offset and animation.
#### Additional Information
- `typescript-plugin-css-modules` 5.3.0 brings its own `sass` 1.107.1.
The build still uses the direct `sass` 1.97.3.
- uuid (CVE-2026-41907, via `@storybook/test-runner`) and certifi
(`tests/uv.lock`) are left for a follow-up.
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- Adds `@SigNoz/nerve` as code owner for the AI Assistant frontend
(`container/AIAssistant`, `hooks/aiAssistant`, `pages/AIAssistantPage`).
#### Issues closed by this PR
#### Screenshots / Screen Recordings
#### Additional Information
- `@SigNoz/nerve` has no write access to this repo, so GitHub ignores it
as an owner until an org admin grants it. // @therealpandey
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- A row click in the traces explorer list opens the span in a side
drawer. The trace page no longer opens for every span. The drawer sits
behind the `TRACES_SPAN_DRAWER_ENABLED` localStorage flag.
- The drawer reuses the trace details span panel, so the overview,
events and logs tabs match the trace page. The up and down buttons and
arrow keys step through the rows. A click outside the table closes the
drawer.
- The list rows have no trace start and end time. The drawer fetches the
trace summary in the background for them. The % exec time shows when the
summary loads. The logs wait for it and query the trace window ±5 min.
- When the summary fails, the panel falls back to the span. The logs
query the span window ±5 min, and the % exec time is hidden.
- The trace ID now has a copy button instead of a link or a
click-to-copy toast.
#### Issues closed by this PR
Part of https://github.com/SigNoz/events-pod/issues/195
#### Screen recording
https://github.com/user-attachments/assets/6de21fc0-da64-4068-9f33-ee137b2de1d5
sbshot:
https://claude.ai/artifact/VBp6qqxdkTxR7ZUVXopY2X?sk=jsc4RO2UvZSzUXBJr1Yk6g
#### Additional Information
- The drawer adds one trace summary call per trace. React-query caches
it, so stepping between spans of one trace does not call it again.
- The drawer closes when the open span leaves the list, for example on a
refresh with a relative time or a page change. The infinite scroll PR
reworks the list query.
- Linked spans are matched by span ID.
- `useCloseOnOutsidePress` is a new shared hook. The logs drawer and
saved views will use it next.
- The explorer story moved to `Pages/Traces/Explorer/Overview`, so its
story IDs change.
#### Description
- Thread tab shows a dot when the trace summary has an `ai` block, so AI
traces stand out before the tab is opened.
- New `Trace Detail: Thread empty with AI summary` event fires when a
trace has AI usage but the Thread tab shows its empty state, to measure
how often AI traces cannot be shown as a thread.
- The event carries the trace summary (root service, entry point, spans,
duration, errors, missing spans, AI tokens, cost) to find what those
traces have in common.
#### Issues closed by this PR
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
https://github.com/orgs/SigNoz/projects/59/views/18?pane=issue&itemId=292126938&issue=SigNoz%7Cnerve-pod%7C583https://signoz-team.slack.com/archives/C0AMEPC0DFV/p1793636954294369
#### Screenshots / Screen Recordings
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
https://github.com/user-attachments/assets/e675aefd-9581-43c5-b4fa-afe5ea5e35b8
#### Additional Information
- Stacked on #13358.
- Fires once per trace, like the thread `Data loaded` event; the summary
is reused, not fetched again.
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- ClickHouse errors 170 (`BAD_GET`), 288
(`DISTRIBUTED_IN_JOIN_SUBQUERY_DENIED`) and 407 (`DECIMAL_OVERFLOW`)
from raw SQL came back as 500s, though each is a fixable query mistake.
They now return invalid input with the fix as a suggestion.
#### Additional Information
- 170 and 407 and their fixes were verified on ClickHouse 25.12. 288
needs a multi-shard cluster, so its suggestion follows the
`distributed_product_mode` docs.
- Like the existing entries, these also apply to SQL SigNoz generates.
- Part of SigNoz/nerve-pod#465.
#### Description
- `app-shell/sidenav` TC-03: dnd-kit stops every click for 50 ms after a
drop, so the Save click that follows the drag was sometimes lost and no
preference request went out. `saveShortcuts` now clicks again while the
dialog is still open.
- `messaging-queues/kafka` TC-01: the health check button is a `loading`
antd `Button` while its onboarding queries fetch, and a loading antd
button ignores clicks without being disabled. `openHealthCheck` now
clicks again until the dialog opens.
- Both tests came in with #13189 and have been failing or needing
retries in CI on unrelated PRs. Notes added to
`tests/e2e/docs/flake-notes.md`.
#### Description
- In the V2 panel editor, "Add query" always created a Metrics query:
`createNewBuilderQuery` fell back to `DataSource.METRICS` because the
panel editor passes no fixed `initialDataSource`. A logs or traces panel
got a metrics query B.
- The new query now continues the last query's signal (and its `source`,
so a meter query stays meter). Hosts with a fixed data source
(explorers, alerts) are unchanged.
- No story adds a query, so no story renders differently.
#### Issues closed by this PR
Closes SigNoz/pulse-pod#659
#### Additional Information
- Stacked on #13315; #13313 (query builder e2e) sits on top and covers
this with a regression test.
#### Description
- **Legacy having:** the old having filter stored `COUNT()` (upper-cased
function). `convertHavingToExpression` passed it through and the v5
backend rejects it with a 400 (`Invalid references in Having expression:
[COUNT]`), so any saved legacy query with a having failed to run. Only
the function name is now lower-cased; attribute keys keep their case.
- **Limit:** a negative limit was sent as-is and the backend returned a
400. The query and formula Limit inputs now accept digits only (`-10`
shows as `10`), so the shown value is always what's sent. Empty or `0`
means no limit.
- `InputWithLabel` gains an optional `sanitize` prop for this.
#### Issues closed by this PR
Closes SigNoz/pulse-pod#657
#### Additional Information
- Found by the query builder e2e suite (#13313), where both cases are
`test.fixme` until this merges. The negative-limit case should expect
the sign to be dropped.
#### Description
New stats from `/api/v1/stats`:
- `telemetry.traces.gen_ai.request.model.exists` – a span carried a
request model since the start of the previous day.
- `telemetry.traces.gen_ai.tool.name.exists` – same, for a tool name.
- `telemetry.traces.gen_ai.agent.name.exists` – same, for an agent name.
- `llmpricingrule.custom.count` – pricing rules users added themselves.
- `llmpricingrule.override.count` – synced default rules users pinned.
- `llmpricingrule.unmapped_model.count` – models seen in that window
that no rule prices.
- `dashboard.panels.ai.count` – panels with an AI builder query.
The gen_ai values come from the traces tag table through the metadata
store, never from a span scan. The model list is capped at 100 names,
which is more than any org is expected to use in two days; past the cap
the unmapped count is a lower bound.
Existing stats change: every `dashboard.*` count now skips system
dashboards, and AI queries no longer count under
`dashboard.panels.traces.count`.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/nerve-pod/issues/577
#### Additional Information
- `/api/v1/stats` latency against `main` on the same ClickHouse with
200k spans: sequential p50 14 ms → 21 ms, at 10 concurrent callers p50
78 ms → 101 ms. The three lookups run concurrently and cost about 8 ms
each regardless of limit.
- If ClickHouse is unreachable the rule counts are still reported and
only the gen_ai keys are omitted, so the reporter keeps their last
value.
#### Description
- The "shows Delete with more than one query and deletes on click" test
from [#13189](https://github.com/SigNoz/signoz/pull/13189) hits jest's 5
s timeout in CI. It has failed most `jsci` runs since it merged,
including merge-queue runs for unrelated PRs.
- It rendered the whole `QueryBuilderV2` with two queries only to make
the Delete item show. It now renders one `QueryV2` with
`queriesCount={2}`, which takes it from about 0.8 s to 0.3 s locally.
- The query actions menu tests find the trigger and items by the test
ids `Dropdown` already sets, in place of a class selector and role
lookups.
- The Delete test keeps a 15 s timeout as headroom for CI load.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/685
#### Description
- The packages guide asked for one- or two-character receivers, which is
what the older half of the codebase does; the newer layers use the
type's role name (`provider`, `module`, `handler`, `store`). The guide
now requires the latter.
- Settles the import-shadowing case the same way
`signozapiserver/registry.go` already does: alias the import, keep the
descriptive receiver.
<!--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
- moving aws and azure integrations to use foundry commands for
integration connection initiation
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
contributes to https://github.com/keystone-pod/issues/167
#### Description
- New `Trace Detail: Span details opened` event, fired once per span
shown in the details panel (click or `spanId` deep link) on both the
Overview and Thread tabs.
- Thread tab now fires the existing `Trace Detail: Data loaded` event,
with the trace-level attributes it has: `totalSpansCount`,
`traceDurationMs`, `hadErrors`, `spanPanelVariant`.
- Both events carry `detailsTab` (`overview` | `thread`) so the tabs can
be split in analytics.
#### Issues closed by this PR
ClosesSigNoz/nerve-pod#581
#### Screenshots / Screen Recordings
Before -
https://github.com/user-attachments/assets/ef1c188c-898b-4a46-b113-39215d5e93d1
Now -
https://github.com/user-attachments/assets/844dbb67-343c-4cd1-9947-d3c9c40a5e62
#### Additional Information
- Named `detailsTab`, not `tab`: `tab` already means the span panel's
tab on other trace-detail events.
- Data loaded fires once per trace per tab; the guard ref lives in the
page, so revisiting a tab doesn't re-fire it.
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- New and edit channel forms now scroll inside the configuration pane,
with Discard / Test / Save pinned to the bottom. Before, the pane's
hidden overflow cut the form off, so the bottom fields and the actions
could not be reached on shorter screens.
- Playwright spec opens both forms in a 600px-tall viewport, wheels to
the end and checks the last control is reachable and sits above the
pinned actions. It fails without the fix.
- The existing ChannelsNew and ChannelsEdit stories already render these
pages.
#### Description
- Page titles now read "All routing policies" / "All planned downtimes"
and the create buttons are title-cased, matching Notification Channels.
- Channels and Alerts Silenced show as many chips as fit the cell, then
a "+N" chip that lists the rest on hover; a long name truncates with an
ellipsis. Dropped the native `title` that doubled the badge tooltip.
- Resizing a column now moves only its right edge. The name column no
longer fills; the column before Actions does, via a new opt-in
`passLastColumnFill` on the shared table, so other tables are unchanged.
- Channels is now the second column in Routing Policies.
- Stories updated: the first routing policy and downtime now overflow
into "+N" in their Default stories.
#### Additional Information
- Truncation sets `--badge-max-width` on the cell, because the global
override in `styles.scss` outranks Badge's `maxWidth` prop.
- Notification Channels has the same column-fill setup and likely the
same resize bug; not changed here.
Closes: https://github.com/SigNoz/pulse-pod/issues/670
#### Description
- `e2eci` now also runs in the merge queue, so the `e2e` check is
reported on merge queue commits.
- In the queue, a `merge-queue-pass` step posts a notice, the e2e jobs
are skipped, and the `e2e` check passes. E2E has already gated the PR by
then, the same way integration tests run on PRs and never in the queue.
- Without this, making `e2e` a required check stalls the queue. Merge
queue commits never get an `e2e` check, so every PR waits out the
60-minute timeout and is removed with `checks_timed_out`. This happened
on 2026-11-02 until `e2e` was made optional again.
- Once this merges, `e2e` can be required again. This PR's own merge
queue run should show a passing `e2e` check on its
`gh-readonly-queue/...` commit.
#### Description
- Adds a driver kit in `tests/e2e/drivers/`: one module per feature that
owns its locators, seeding and navigation. A spec imports one module and
calls no `page.locator`/`getBy*` itself. `tests/e2e/helpers/` and
`tests/e2e/fixtures/` are removed.
- Moves the existing specs onto the kit and adds specs for alerts, logs,
traces, APM, metrics and meter, infra monitoring, integrations, LLM
observability, settings, auth, SSO, onboarding and the app shell. 86
spec files become 125.
- Keeps the dashboards suite on `main` and its drivers. Adds only what
it lacks: row rename, tags and duplicate, section clone and delete,
stack modes, viewer permissions and public dashboards. The channel specs
on `main` stay as well.
- Checks that need no browser go to jest or pytest integration tests
instead, following the layer rule in `tests/e2e/AGENTS.md`.
- Fixes three frontend bugs the suite hit:
- the logs explorer requested the next infinite-scroll page again when
the end was reached before it loaded
- the side nav showed the default pinned shortcuts instead of the saved
ones when user preferences loaded late
- TanStack tables read `undefined` from column state saved by an older
version
- Adds the testids the specs select by, and turns on metrics reduction,
which the bootstrap conftest had been shadowing. Known flakes and their
workarounds are in `tests/e2e/docs/flake-notes.md`.
| Playwright (chromium) | `main` | This PR |
| --- | --- | --- |
| Tests | 425 | 647 |
| Spec files that run | 86 | 125 |
| Feature areas with specs | 9 | 27 |
- Dashboards goes from 185 tests to 196.
- Alerts goes from 195 tests to 118. Checks that need no browser
(thresholds, labels, edit prefill, the downtime form) move to jest, and
34 spec files merge into 16.
- jest gains 148 tests: 15 new test files, 20 extended. Integration
gains 2: `listErrors` grouping exception events, and logout revocation.
#### Issues closed by this PR
Closes#9135Closes#3849Closes#9226
#### Additional Information
- Easiest to review commit by commit, and each commit typechecks on its
own. One commit per concern: config, agent rules and docs, drivers
(about 17,800 lines), specs, testids, jest, then the fixes with their
integration tests.
- Product bugs found and left for separate fixes. The specs allow for
each one, and the allowance should go when the fix lands:
- `DeleteToken` drops the cache entry before the row, so a concurrent
lookup can re-cache a revoked token for up to 30 days.
- `/api/v4/metric/metric_metadata` returns 500 instead of 404 for a
metric that does not exist, so the Apdex panel errors on any setup
without span metrics.
- The LLM observability overview calls the public-dashboard and authz
APIs with its synthetic dashboard id, and both return 400.
#### Description
- `makeBuckets` looped forever (and OOM-killed the query service) when
`start` was 0: `curEnd - bucket` wrapped below zero and never reached
`start`. Each bucket now starts at `start` once the remaining range
fits, so the loop always ends. A reversed window (DESC cursor reaching
the start boundary) yields no buckets instead of wrapping.
- `start`/`end` are coerced to epoch ms at decode, so validation
compares like units; `QueryRange` still coerces for in-process callers
(domain list/info).
- Validation rejects a missing/zero `start`, `start >= end`, and an
`end` more than 24h in the future (e.g. a 30-day duration read as a
seconds epoch → year 2052). These are well-formedness checks:
unconditional, no config, no opt-out.
- Integration tests freeze the rejections and the step clamp for a
2-year span at a 1s step (logs/traces 42045s, 1502 buckets; metrics
210600s).
#### Issues closed by this PR
Fixes https://github.com/SigNoz/events-pod/issues/178
#### Additional Information
- Behavior change for API clients: `/api/v5/query_range` now returns 400
for a zero `start` or an `end` more than 24h ahead.
- Addresses the validation half of
https://github.com/SigNoz/events-pod/issues/207. Capping how wide a
window may be is deployment capacity rather than well-formedness, so it
is split into SigNoz/signoz#13324, which closes that issue.
#### Description
- Move `/api/v2/rule_views` routes from role gates to `CheckResources`
on `rule:list`, dashboard views parity.
- Migration 143 backfills rule tuples for managed roles, the first PR in
the stack to check a rule scope.
- Integration tests cover a viewer on all four routes and a custom role
without `rule:list`.
#### Issues closed by this PR
Part of SigNoz/pulse-pod#286
Part of SigNoz/pulse-pod#355
#### Additional Information
Third of five stacked PRs splitting #12927. Stacked on #13250.
#### Description
- Switching channel type now clears the previous type's prefilled
templates that the new type doesn't set, so e.g. Slack → Telegram no
longer sends Slack's title, and PagerDuty → Opsgenie no longer sends a
hidden `details` map. `sendResolved` is kept.
- PagerDuty no longer prefills Client URL with a dead placeholder host;
left empty, the backend links to the instance. Help text says so.
- No page story covers the channel form; the change affects form values,
not layout.
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#669
#### Description
- A span attribute metadata does not report fell back to the legacy maps
in every window. It now follows the JSON rollout like a reported key.
- The traces storage reads the column-wide evolution rows in `Fallback`,
where those keys are synthesized, so callers and requested keys stay
untouched.
- The rows come from a separate `SchemaMetadataStore`, built once next
to the metadata store and shared by every traces storage. Found rows are
cached; an empty result is not, since the migrator can add the row while
the service runs.
#### Additional Information
- Alternative to #13168, which carried the rows on the requested key
through every caller.
- The storage gets only `SchemaMetadataStore`; `MetadataStore` is
unchanged.
- New `querier.schemametadatastore.cache_ttl` (default `0`, cached for
the life of the process). The integration server sets `1ns` since its
tests reseed the rollout row between cases.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01SXgj9vFWJku1UdD6MDZTmb
<!--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
- `ubuntu-latest` now resolves to Ubuntu 26.04, which no longer ships
`apt-key`, so the webdriver step failed before any test ran on the
basepath and callbackauthn suites.
- The Chrome signing key is dearmored into `/usr/share/keyrings` and the
apt source references it with `signed-by=`, which works on both 24.04
and 26.04 images.
<!--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/672
#### Description
- Adds the `signoz/ScatterPlotPanel` kind, which draws one dot per group
placed by two aggregations, per the Scatter Plot PRD.
- `dimensions.x/y/size` reference value columns by the Table column key
(`A` or `A.<expression>`); `dimensions.color` is a group-by label. All
four are optional and the renderer picks defaults when they're empty.
- `formatting` reuses `TableFormatting`, so `columnUnits` carries over
when switching between Table and Scatter Plot.
- Axes are per-axis with a new `scale` enum (`auto` | `linear` | `log` |
`symlog`, default `auto`); the shared `Axes` type is unchanged.
- Allows every query kind, PromQL included. Public dashboards turn on
`formatTableResultForUI` for this kind so rows from all queries are
joined by group, as for Table.
#### Additional Information
- The generated TS schemas aren't in this PR: adding the kind breaks the
exhaustive panel-kind maps in the V2 dashboard code. The frontend PR
will regenerate them and register the panel.
Closes https://github.com/SigNoz/pulse-pod/issues/576
---------
Co-authored-by: Naman Verma <naman.verma@signoz.io>
#### Description
- The role wizard's Key field becomes a dropdown of the supported
telemetry grant keys, `signoz.workspace.key.id`, `resource.service.name`
and `resource.deployment.environment.name`, instead of a fixed value. A
typed selector selects its key in the dropdown, and
`resource.signoz.workspace.key.id` folds to the bare workspace key as
the backend does.
- Covered by the Role Editor Storybook story (`Telemetry Key Dropdown`,
`Telemetry Key Scoped`).
#### Issues closed by this PR
Contributes to SigNoz/keystone-pod#73
#### Description
- Move `/api/v1/downtime_schedules` routes from role gates to
`CheckResources` with resource defs.
- Create and update check `rule:attach` on every id in `alertIds`.
Update diffs against the stored schedule: attach for added, detach for
removed.
- Migration 142 backfills planned-maintenance tuples plus attach and
detach on planned-maintenance and rule for admin and editor.
- Integration tests cover viewer, editor, admin, and a custom role
without rule attach or detach.
#### Issues closed by this PR
Closes SigNoz/pulse-pod#289
Part of SigNoz/pulse-pod#355
#### Additional Information
Second of five stacked PRs splitting #12927. Stacked on #13249.
---------
Co-authored-by: Naman Verma <naman.verma@signoz.io>
#### Description
- The traces explorer list view no longer sends the selected columns as
`selectFields`. The API now returns every span field for each row. The
span details drawer in the next PR uses this data, so it can open
without another request.
- Adding, removing or reordering a column no longer refetches the list.
- The table reads attribute and resource columns from the `attributes`
and `resource` maps on each row. It uses `getSpanAttribute`, the same
lookup as trace details.
- `getSpanAttribute` now keeps `false` values. Before, boolean `false`
attributes showed "-" in the table and dropped out of the trace details
hover previews. Color-by now treats `0` and `false` as real values, not
`unknown`.
#### Issues closed by this PR
Part of https://github.com/SigNoz/events-pod/issues/195
#### Screenshots/Screen recording
No refetch on add/remove columns
https://github.com/user-attachments/assets/799b39b3-3594-441e-b5dc-0c8177c29afb
All fields fetched
<img width="1687" height="1000" alt="all fields fetched"
src="https://github.com/user-attachments/assets/af8dd374-6b30-40a5-92dc-caa07defc463"
/>
#### Additional Information
- Two backend gaps show "-" in some columns:
- `scope.name` and `scope.version`: the all-fields mode does not select
the `scope` column. Value suggestions for these fields are also empty.
The fix is to add `scope` to `ContextualSpanColumns`.
- Old camelCase column names (`durationNano`, `httpMethod`, ...) in
saved views and column preferences: the backend resolved them only when
they were in `selectFields`. The fix is a backend migration that renames
them. The workaround is to remove the column and add it again.
- Each row is larger, because the response carries all span fields.
- Export and add to dashboard still use the selected columns.
- The List view story rows now use the nested `resource` shape. The page
renders the same, so the existing story covers it.
- cc. @tewarig
#### Description
- Telemetry grants can now scope on `resource.service.name` and
`resource.deployment.environment.name`, alongside
`signoz.workspace.key.id`.
#### Issues closed by this PR
ClosesSigNoz/keystone-pod#73
#### Description
- Waterfall spans and the empty-`selectFields` list view now return
`scope: {name, version, attributes}`, read from the `scope` JSON column.
- Flamegraph `selectFields` accept `fieldContext: scope`: `scope.name` /
`scope.version` resolve to the declared paths, any other name to a scope
attribute, the same way the query builder resolves them.
- Scope attributes are flat dotted keys, like span attributes.
- Span details drawer shows `scope`.
#### Issues closed by this PR
Closes https://github.com/SigNoz/events-pod/issues/220
For Frontend changes https://github.com/SigNoz/events-pod/issues/230
#### Additional Information
- Stacked on #13274.
- API contract change: OpenAPI spec and frontend client regenerated in
their own commits.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#### Description
- Support for camelCase trace field names (`durationNano`,
`serviceName`, `httpMethod`, …) in select fields is being removed.
- Adds migration 141, which renames them in traces saved views'
`spec.selectedFields` using the same mapping as `oldToNew`.
- Drops `fieldContext` on renamed entries: legacy columns carry
`attribute` from migration 114, which would make `service.name` resolve
to a span attribute. Without it the query builder resolves the field
itself.
- If the new name is already selected, the renamed entry is dropped.
Unparseable rows are logged and skipped.
- Prod backups: 52 traces views across 29 deployments still use these
names, mostly the old default columns. Tested on a restored backup by
running migrations 049–141 through the server.
#### Additional Information
- Logs views and old names in filters / orderBy / groupBy are not
touched.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
#### Description
- Key options menu, saved view options and preference slices by saved
view source page (`SavedviewtypesSourceDTO`) instead of the query's
`DataSource`.
- `PREFERENCE_SOURCE_PAGES` (logs, traces) is the one list of pages with
column and formatting preferences; the type and the
`isPreferenceSourcePage` guard come from it.
- Saved-view behavior on metrics and meter is unchanged: the hooks still
fall back to the logs options and drop them where the source has no
preferences.
- Tests use typed mocks (`jest.mocked`, `jest.MockedFunction`) and check
preference fixtures with `satisfies PreferenceContextValue`.
#### Issues closed by this PR
#### Screenshots / Screen Recordings
- [sbshot visual
diff](https://claude.ai/artifact/9yZWJ8t5J9vX65n43m2cK1): 188 Storybook
pairs (logs, live tail, traces, saved views, fields selector, metrics,
meter; dark + light) vs merge-base. 170 identical; 18 moved, all noise
(wall-clock mock timestamps, spinner frames, antialiasing).
#### Additional Information
- Review commit by commit: preferences → options menu → saved views. The
layers depend on each other, so only the last commit builds.
- `PreferenceContextValue` gains `ai_observability` in #13297; the
`useOptionsMenu` fixtures get the AI slice there.
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- `Components/Query Builder`: the builder as each host mounts it (logs,
traces, metrics and meter explorers, V2 panel editor, logs alert), props
mirrored from each call site and the starting query seeded through the
route.
- A `QueryProbe` below the builder prints the `query_range` payload its
live state would send, so plays assert on the query instead of the DOM.
- `Components/Query Builder/Behaviour`: add-ons per panel kind and
signal, metric-type defaults (gauge → avg/avg, histogram → p90) and the
single-aggregation limit on Value panels.
- Shared msw handlers now answer `/api/v1/fields/values`,
`/api/v2/metrics`, `/api/v2/metrics/metadata` and the `ai_observability`
fields, so any story rendering the builder has them.
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#663
#### Additional Information
- Filter suggestion loading/empty/error states are already covered by
the logs page stories, so they are not repeated here.
- The test runner reports existing a11y warnings in the builder's
markup; out of scope here.
#### Description
- `PanelTypeSelectionModal/` was 20 flat files. Each component now lives
in `components/<Name>/` with its styles, and the picker-target hooks in
`hooks/`. The drawer, section-option `types`/`utils` and the test stay
at the root.
- `PanelTypeTile` gets its own stylesheet, split out of
`PanelTypeBrowser.module.scss`.
- No behaviour change. Outside the folder, only `PanelTypeSwitcher`'s
import path changes.
#### Description
- Langfuse SDK spans reached the thread with empty formatted messages:
the SDK writes them to `langfuse.observation.input/output`, which the
mapper never moved onto `gen_ai.*.messages`.
- `gen_ai.llm` v5 moves `langfuse.observation.input/output` onto
`gen_ai.input/output.messages` and copies `langfuse.observation.type`
onto `gen_ai.operation.name`, above every existing source. New spans
only.
- Formatter handles the shapes these spans carry: a superjson `{json}`
envelope, a single message object (incl. role-less `{tool_calls}` from
the LangChain handler), a bare list of Responses items (LiteLLM
`langfuse_otel` tool turns), and falls back to the raw output when the
OpenInference bucket is empty. Mixed-format warning dropped when one
side is generic or OpenInference.
#### Additional Information
- Verified with unit tests, the shipped definition through the
in-process collector pipeline.
- Why Langfuse keeps its own keys instead of emitting semconv, checked
in the Langfuse repos:
- Docs ([OpenTelemetry
integration](https://langfuse.com/integrations/native/opentelemetry)):
`langfuse.*` attributes "always take precedence over the generic
OpenTelemetry conventions" and are "recommended for all users that are
manually instrumenting"; semconv is "still evolving", so OTel traces are
mapped into the Langfuse model.
- [Discussion #2509](https://github.com/orgs/langfuse/discussions/2509),
Sep 2024, maintainer: Langfuse features "not yet covered by the
conventions" and the risk of "breaking backwards compatibility once
conventions are in place" are the reasons for an own namespace.
- Ingestion (`OtelIngestionProcessor.ts`, `extractInputAndOutput`): own
keys first, then vendor keys, then semconv `gen_ai.*.messages`, then
events. Semconv is one vendor format among many.
- SDK sources: input and output are any JSON on any observation type
(span, agent, tool, generation, …), looser than `gen_ai.input.messages`.
- Still unmapped: `usage_details`, `cost_details`, `model.parameters`
are JSON objects; the mapper cannot extract fields, so pure Langfuse
spans have no tokens or cost.
Closes https://github.com/SigNoz/nerve-pod/issues/579
<!--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 basically does two fixes:
- Remove loading effect
- The effect was not looking good, this was implemented during the
migration of components.
- Remove read checks for each item
- We should not have loading for each item in the list, against our
guidelines for authz
- The list already returns the info needed in read, redundant to have
both checks
- Replace the button with a link
- To improve UX of this views, they were suppose to act as links.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/uforge-pod/issues/6394
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
Normal view:
<img width="1680" height="1224"
alt="dark--components-saved-views-authz--no-read-open-view--blink (1)"
src="https://github.com/user-attachments/assets/68772cda-11f1-4798-8012-79bfe685cbcd"
/>
No read only:
<img width="1680" height="1224"
alt="dark--components-saved-views-overview--unsaved-changes--blink (1)"
src="https://github.com/user-attachments/assets/898ab59a-f870-4d7c-8c2a-e20843f98444"
/>
Long names:
<img width="1680" height="1224"
alt="dark--components-saved-views-overview--long-names--blink (1)"
src="https://github.com/user-attachments/assets/48a4bd61-8335-4047-8c2a-eca42c43653e"
/>
No read only 2:
<img width="1680" height="1224"
alt="dark--components-saved-views-authz--no-read--blink (1)"
src="https://github.com/user-attachments/assets/af04bb90-1ce7-4c36-818b-49e26933ea0b"
/>
Full diff:
https://claude.ai/artifact/S43YyxbMmCdtZLmEXVNsgm?sk=LXctCOiBiN5kGigap2M_JA
#### Description
> TLDR: You can take a look at
https://pr-docs.viniciusl.com.br/signoz-router-facade/
- Prepares the react-router v5 to v6 upgrade. Every router call now goes
through `src/lib/router`, so the upgrade touches one directory instead
of ~300 call sites. Still on v5, no behavior change intended.
- The facade is typed to v6 semantics (`string | undefined` params,
history@5-style blocker with `retry`, `replace`-by-default redirects).
The v5 implementations adapt up to it.
- `signoz/no-direct-react-router-import` (error) stops new code from
importing `react-router-dom`, `react-router-dom-v5-compat` or
`lib/history`, and from mocking them in tests.
What replaced what:
| Before | After |
| --- | --- |
| `useHistory().push/replace` | `useAppNavigate()` |
| `lib/history` singleton outside components | `navigate`, `back`,
`getCurrentLocation`, `subscribe`, `blockNavigation` in
`lib/router/navigation` |
| `useLocation`, `useParams` | `useAppLocation`, `useAppParams` |
| `Link`, `Redirect` | `AppLink`, `Redirect` |
| `matchPath` | `matchRoute` |
| v5-compat `useSearchParams`, `useNavigationType` |
`useAppSearchParams`, `useAppNavigationType` |
| `withRouter` HOCs (3) | removed |
| `jest.mock('react-router-dom')` (48 files), `MemoryRouter` |
`render(ui, undefined, { initialRoute, routePath })`, or
`tests/router-mocks` spies |
Direct `react-router-dom` importers go from 268 to 19, all in the facade
or the lint allowlist.
#### Issues closed by this PR
Closes https://github.com/SigNoz/uforge-pod/issues/6279
#### Additional Information
Reviewing: 397 files, but most are mechanical import swaps. Read by
commit.
- Read closely: `src/lib/router/` (facade plus its tests). Check the v5
to v6 adaptations: `AppLink`/`Redirect` state handling and `replace`
defaultng `state: undefined`, `blockNavigation` returning `false`,
`useAppParams` typing.
- Read closely: the test harness (`tests/router.tsx`, `router-mocks.ts`,
`test-utils.tsx`). Tests now mount on the `lib/history` singleton, so
imperative `navigate()` moves the test router.
- Read closely: the lint rule and the allowlist in `.oxlintrc.json`.
- Not mechanical: `useNavigationBlocker` (moved to `blockNavigation`),
the `withRouter` removals, and no longer threading `History` through
props.
- Skim: the `refactor(router): route ... through the facade` and
`test(router)` commits. Verify with `git grep "react-router-dom" -- src`
instead of
Testing:
- Unit tests for each facade module, plus the repointed Jest suites.
- The routing e2e suite from #13078 runs unchanged against this branch
and covers what the facade owns: router/nuqs param coexistence,
imperative namatching and redirects, auth guards, the unsaved-changes
blocker, router mount.
<!--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
- Typing an email (e.g. `user@example.com`) in the NOZ composer opened
the `@` context picker mid-word and interrupted typing.
- `@` now opens the picker only at the start of a word (start of input
or after whitespace).
- Added tests for both cases.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
https://github.com/orgs/SigNoz/projects/59/views/18?pane=issue&itemId=257450003&issue=SigNoz%7Cnerve-pod%7C381
<!--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://www.loom.com/share/d09ee6fa24234f148e9a929a28af64cc
Now -
https://github.com/user-attachments/assets/934f3a6e-ece9-4863-9064-6daa2c73db8f
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- No story change: typing behaves differently, but nothing renders
differently.
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- Removes frontend dependencies that nothing uses: `react-beautiful-dnd`
and its types, `vite-plugin-html`, `rc-select` (only a type came from
it, and antd exports it as `RefSelectProps`), `@types/uuid` (uuid 14
ships its own types), `glob` (the i18n hash script reads the folder with
`fs`) and `is-ci` (`postinstall` checks `$CI`, which the Makefile and
the Docker build already set).
- Upgrades `husky` from 7 to 9. The hook files drop the v7 header lines.
- Upgrades `@types/node` from 16 to 22 to match `engines.node`. Its
dependency `undici-types@6.21.0` has no provenance, which pnpm's trust
policy reads as a downgrade, so it is listed in `trustPolicyExclude`.
- Deletes `hooks/useInterval`, which only its own test imported.
#### Issues closed by this PR
Closes https://github.com/SigNoz/uforge-pod/issues/6406
#### Additional Information
- Overlaps #12601 (draft), which replaces husky with `.githooks`.
Whichever merges second rebases `postinstall` and `.husky/`.
- Verify with `pnpm install && pnpm build`, then commit with a bad
message: the commit-msg hook still rejects it.
#### Description
- Adds `data-testid`s to Query Builder controls that had none, ahead of
the QB E2E suite: the filter, aggregation, having and trace-operator
CodeMirror editors, the query/formula header buttons, the query actions
menu, formula inputs, metric time/space aggregation selects and the
panel editor query-type tabs.
- Each query and formula block is keyed by name (`query-A`,
`formula-F1`), so child ids stay unindexed
(`getByTestId('query-B').getByTestId('having-editor')`) and don't shift
when queries are cloned or deleted.
- Attribute-only change: nothing renders differently, so no story
update.
#### Description
- Routing Policies and Planned Downtime now use the same full-width
table as Notification Channels, replacing the centred collapsible cards
that hid most fields.
- Both tables have a column picker, sortable columns, row click to edit,
a row actions menu, and empty/no-results/error states. Audit columns
(and description, for Planned Downtime) are hidden by default.
- Planned Downtime shows the timezone in Schedule and the per-occurrence
duration in Repeats, so the default columns fit on screen with the
actions visible.
#### Additional Information
- Stacked on #13230; review that first.
- Planned Downtime search is now case-insensitive.
Closes https://github.com/SigNoz/pulse-pod/issues/255 ,
https://github.com/SigNoz/pulse-pod/issues/256
#### Description
- Playwright planner/generator/healer agents referenced deleted files
(`helpers/dashboards.ts`, `tests/dashboards/list.spec.ts`) and a removed
dashboard template form; they now point at `drivers/`,
`helpers/common.ts` and the golden dataset.
- Generator writes specs against drivers and adds/extends one when
missing; gets `Write`/`Edit` for that.
- Healer fixes locators and waits in the driver, not the spec.
- Contributing guide: driver `dispose` is the preferred cleanup; stale
`list.spec.ts` example replaced with `llm-o11y/llm-pricing.spec.ts`.
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#656
#### Description
- Sentry gets errors from third-party scripts we load: PostHog's session
recorder (served from `*.posthog.com`), the Pylon widget and Appcues. We
can't fix them, and each one also uploads a session replay
(`replaysOnErrorSampleRate: 1.0`).
- `beforeSend` now drops an event when any stack frame comes from one of
those hosts. It checks every frame because a failed recorder fetch ends
in Sentry's own fetch wrapper, which is part of our bundle, so
`denyUrls` (innermost frame only) would miss it.
- posthog-js itself is bundled, so when its recorder script fails to
load, the error points at our own files. That one is matched by message
in `ignoreErrors`.
- In production, `beforeSend` also drops an event when no stack frame
comes from our `/assets/` bundle: browser extensions, or a script
another tool appends inline to the page (one Safari `SyntaxError` had
only the page URL and `[native code]` frames). An event with no
positioned frame is kept.
- The ignore list and both frame checks live in
`AppRoutes/sentryFilters.ts`, with unit tests.
Over the last 30 days the host check would have dropped about 70 events
across three Sentry issues. No error from our own code matched it.
#### Issues closed by this PR
Closes https://github.com/SigNoz/platform-pod/issues/2778
#### Additional Information
- Verify with `pnpm jest src/AppRoutes/__tests__/sentryFilters.test.ts`.
- The bundle check is off in dev, which serves source from `/src/`.
<!--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
Fix this flaky test causing many merges in the queue to be rejected due
to this flaky.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/uforge-pod/issues/6405
#### Description
Consolidates alerting configuration views (**Notification Channels**,
**Routing Policies**, and **Planned Downtime**) under a unified
**Configuration** tab on the Alerts page, navigated via a flat-list
secondary sidebar.
#### How to Test
1. Navigate to `/alerts` and click the **Configuration** tab:
- Should default to `/alerts?tab=Configuration&subTab=channels` with
Notification Channels rendered.
2. Click **Routing Policies** and **Planned Downtime** in the secondary
sidebar:
- Verify the URL updates `subTab` accordingly and renders the correct
view.
- Verify secondary sidebar items have symmetric horizontal spacing,
hover states, and no robin cyan marker bar.
3. Test legacy URLs:
- Access `/alerts?tab=RoutingPolicies` or `/alerts?tab=PlannedDowntime`
directly; verify it switches to the Configuration tab with the
appropriate sub-view active.
4. Test channel workflows:
- Navigate to `/alerts/channels/new` and ensure the form renders inside
the Configuration tab view.
<img width="1915" height="955" alt="Screenshot 2026-10-29 at 12 42
43 PM"
src="https://github.com/user-attachments/assets/77f585f0-d64e-4cf7-beda-ce18fbb08472"
/>
#### Description
- Move `/api/v1/route_policies` routes from role gates to
`CheckResources` with resource defs, same shape as dashboards (#12408).
- Create and update check `notification-channel:attach` on the channels
the policy names. Update diffs against the stored policy: attach for
added, detach for removed.
- Channel names resolve to ids through a new alertmanager method,
`GetChannelIDsByNames`. Route policy create and update call it too, so
an unknown channel name is rejected by the module like
`validateChannels` does for rules. Before this a policy naming a missing
channel was stored and failed silently at notify time.
- `notification-channel` drops its explicit verb list so attach and
detach come from the metaresource default.
- Migration 140 backfills route-policy tuples plus channel attach and
detach for admin and editor, and resyncs transaction groups like 137.
- Integration tests cover viewer, editor, admin, and a custom role
without channel attach or detach.
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#288
Part of SigNoz/pulse-pod#355
#### Additional Information
First of five stacked PRs splitting #12927: route policies, downtime
schedules, rule views, rule state history, rules.
<!--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
Rules v1 to v2 migration is gonna happen soon, so its better to have at
least our api spec updated so that agents stop creating v1 rules
#### Description
- The new saved views panel and the bottom strip are on for everyone.
This PR removes the old saved views code they replaced:
- the Views tabs and the saved views page on Logs, Traces, Metrics and
Meter
- the `ExplorerCard` and `ExplorerOptions` bars
- the v1 saved views client
- the `SAVED_VIEW_ENABLED` flag
- Old views URLs still redirect to their explorer.
- The explorer URL now carries only `viewKey`. The app no longer writes
`viewName`.
- `ExplorerCard` no longer runs. Its header was hidden, but it still
fetched the v1 views list on every Logs, Traces and LLM explorer load.
- Stories now mock `/api/v2/saved_views`, and the old Views tab stories
are gone.
#### Issues closed by this PR
Closes https://github.com/SigNoz/events-pod/issues/77
#### Additional Information
- The dashboards list status bar and the side nav Noz entry stay. Both
are still live UI, and the status bar holds the only rail toggle.
- Noz no longer gets the view name in its page context. It still gets
the view id.
#### Description
- `StorableSpan.Attributes()` prefers the flattened JSON column per span
and falls back to the legacy maps, so waterfall, flamegraph and thread
share one attribute bag (replaces the thread-only helper and
`AttributeValue`).
- Waterfall and flamegraph always read `attributes`; maps are blanked in
SQL for rows whose JSON is non-empty (`if(notEmpty(attributes), map(),
…)`), so dual-written spans ship attributes once on 10k/100k-span loads
while pre-rollout spans keep their maps.
- Attribute reads are no longer gated on `use_trace_attributes_json`, so
the waterfall store and unexported module helpers drop their unused
`orgID`.
#### Additional Information
- Requires the `attributes` column on `signoz_index_v3` (otel-collector
schema migration); without it waterfall/flamegraph error instead of
falling back.
- Flamegraph `GROUP BY`: the blanking `if` wraps `any(<map>)`, not the
reverse — `any(attributes) AS attributes` makes `attributes` resolve to
the aggregated alias, and nesting it is `ILLEGAL_AGGREGATION`.
- Thread reads `attributes` and the maps as-is (pages are capped at ~100
spans): its gen_ai filter reads `attributes_string` in WHERE, which a
blanking alias would shadow and drop dual-written spans.
- Supersedes #12798.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#### Description
- Drops the Google Chat, Jira and incident.io URL checks the API removed
in #13008, so a proxy or relay in front of those vendors can be saved.
- Webhook auth is now a None / Basic auth / Bearer token radio. Only the
chosen credentials are shown and sent, so a username plus a bearer token
can no longer reach the API and fail.
- A webhook username without a password is now allowed, matching the
API.
- The form now catches what the API still rejects: a Slack field without
a value, a Slack button without a URL or name, and an incident.io token
with a `Bearer ` prefix or surrounding spaces.
- Form errors are now nested by field path, so a Slack row error shows
under its row.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/625
#### Screenshots / Screen Recordings
Visual diff of every `Pages/Alerts/Channels` story, dark and light,
against the merge-base:
https://claude.ai/artifact/GBULJmZFpzhe5bRviSnBoY
- 4 pairs changed: the webhook form on the new and edit pages.
- 16 new pairs, from 8 new stories.
- 125 of 148 pairs are pixel-identical.
- 3 pairs moved for reasons outside this change: a focus ring present in
one run only.
#### Description
- `QueryBuilderV2` pinned the provider's `initialDataSource` to metrics
for bar panels, a leftover from when bar charts were metrics-only. It
has no visible effect today, but implies bar panels are metrics-only.
- Removed it and the AI override that only existed to undo it; the AI
tab already passes a static traces config.
- Added tests for the provider config sync.
Closes https://github.com/SigNoz/pulse-pod/issues/603
#### Description
Adding ai qb examples
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/nerve-pod/issues/289
#### Description
Base #13271, which puts the uPlot instance on the chart container; this
PR no longer touches UPlotChart.
- `Pages/Dashboards/Detail/Panels`: the real dashboard page around a
one-panel dashboard of any of the 10 kinds, with controls for kind,
legend, thresholds, unit, stacking, y-scale, line style, points, gaps,
column units, bucket width, heatmap palette / scale / count range, Text
body / alignment, result (data / empty / warning), series and List rows.
Loading and failed come from the global Data control.
- 26 stories, each with a `play` that asserts what the kind draws: uPlot
series, scales, bands and dash read off the live instance; threshold
lines read from canvas pixels; per-series 100% shares; histogram bins on
the bucket width; heatmap colour-bar endpoints; pie arcs and the exact
total; number value and threshold colour; table cells painted per
column; list pages and the offset each page requests; Text markdown.
- Per review, this is where per-kind chart and config detail lives; the
e2e kind specs (#13233, #13273) keep only flows and smoke checks.
- `ConfigPane.test.tsx` renders each kind's config pane and checks the
sections it shows.
- Shares the document store, public-link meta and warning with the
DashboardPage stories; the table and heatmap `query_range` builders take
a `warning` like the others.
#### Additional Information
- Runs in the existing `storybook` CI job; no screenshots or binaries
committed.
#### Description
- Quick filters took two props for the same page: `source` for the page
and `signal` for the saved filter list. Every page passed a matching
pair.
- Quick filters now take one `quickFilterSource` prop. Its values are
the backend quick filter sources, plus `INFRA_MONITORING`, which has no
saved list.
- The saved filter list request uses the prop value directly.
- One map gives the fields API signal for each source. It replaces
`SIGNAL_DATA_SOURCE_MAP` and the per-filter `dataSource`, so
`dataSource` is removed from the filter configs.
- Requests stay the same on every page, with one exception. The infra
Environment filter had no `dataSource`, so it sent no signal and listed
environments from traces and logs too. It now sends `metrics`, so it
lists only environments that the infra tables can show.
#### Issues closed by this PR
Closes https://github.com/SigNoz/events-pod/issues/70
#### Additional Information
- Three QuickFilters stories (`NoResults`, `SelectedExpandedCheckbox`,
`Tooltips`) now show the settings control, because the logs page always
has a saved filter list.
#### Description
- New anomaly alerts are created in the new alert form and saved as
`schemaVersion: v2alpha1`. The backend already accepts and evaluates
anomaly rules in this shape; only the frontend routing kept them on the
classic form.
- Anomaly alerts are offered only on cloud and enterprise self-hosted
licenses. This replaces the `anomaly_detection` feature check, which the
backend never enforced.
- The anomaly condition reads like the threshold one: a sentence with
the query, direction, match type and window, then the algorithm and
seasonality, then one row per severity with its deviation and channels,
as on threshold alerts. The deviation is a number above 0, so values
like 2.5 work, and ON AVERAGE and LAST join the match types.
- Thresholds are literal z-scores: "below N deviations" is sent as
`target: -N` with an empty `targetUnit`, since a unit would rescale the
z-score. The window uses the threshold window picker, locked to rolling.
- The `anomaly` query function carries the deviation and seasonality, so
the preview band matches what the rule evaluates. Only builder metric
queries are offered.
- Switching between Threshold and Anomaly keeps the query, and the tab
tooltips say what each choice saves. Saved anomaly rules reopen on the
Anomaly tab with their settings.
- Fixed for every alert type: thresholds typed into a new alert were
reset each time the query ran, because the form re-applied its URL
prefill on any URL change. It now re-applies only when a prefill param
changes.
#### Screenshots / Screen Recordings
[Before and after
screenshots](https://claude.ai/artifact/UzhpfdaHJrFePTDAZLAtwD) of every
story under `Pages/Alerts/Create` and `Pages/Alerts/Overview`, in dark
and light: 44 pairs, 8 new screens, 26 changed (24 of them only gain the
Anomaly tab on metric alerts), 10 pixel-identical.
#### Additional Information
- Existing v1 anomaly rules still open in the classic editor, the same
as other v1 rules.
- `anomaly_detection` is still served by the backend plans and listed in
the frontend feature keys and mocks. Nothing reads it any more; removing
it is a separate cleanup.
#### Description
Part of the dashboards E2E stack; base #12410. Ports the old dashboard
detail specs onto the drivers.
| PR | Content | Base | Tests |
|---|---|---|---:|
| #13271 | uPlot instance on the chart container | `main` | — |
| #12409 | drivers, spec builders; drops the old dashboard specs |
#13271 | — |
| #12410 | panel and editor flows | #12409 | 74 |
| #13263 | dashboards list page | #12410 | 24 |
| #13264 | viewing, sections, edit mode, time range, variables, settings
| #12410 | 67 |
| #13233 | Time Series: the one full panel flow | #12410 | 2 |
| #13273 | thin checks for every other panel kind | #12410 | 18 |
Merge #13271, #12409, #12410; the rest are independent of each other.
- `drivers/dashboard-page.ts`: breadcrumb, title, tags, toolbar, Actions
menu, inline rename, lock / unlock, full screen, JSON drawer, sections.
- `drivers/dashboard-settings.ts`: time picker, variables bar (every
variable type), settings drawer (overview, variables, publish).
- `page/viewing`, `page/edge-cases`, `page/sections`, `page/edit-mode`,
`page/time-range`, `page/variables`, `page/settings` (67 tests).
- Also covers cases the old specs only held as `test.skip` placeholders:
Edit panel opening the editor, cancelling a panel delete, a variable
change refetching its panel, the variable description tooltip,
drag-reordering variables and creating a dynamic variable in Configure.
#### Additional Information
- Two product bugs are pinned as `test.fixme`:
- A share link's `?variables=` is ignored on a first visit
(SigNoz/pulse-pod#621).
- A changed textbox default never reaches anyone who opened the
dashboard before (SigNoz/pulse-pod#622).
- Adapted to the current product: collapse is a per-user preference that
survives a reload (no save), Export / Copy JSON live in the JSON drawer,
rename is inline, lock disables rather than hides, names are capped at
128 characters, and a bad dashboard id shows "Failed to load dashboard".
#### Description
Part of the dashboards E2E stack; base #12410.
| PR | Content | Base | Tests |
|---|---|---|---:|
| #13271 | uPlot instance on the chart container | `main` | — |
| #12409 | drivers, spec builders; drops the old dashboard specs |
#13271 | — |
| #12410 | panel and editor flows | #12409 | 74 |
| #13263 | dashboards list page | #12410 | 24 |
| #13264 | viewing, sections, edit mode, time range, variables, settings
| #12410 | 67 |
| #13233 | Time Series: the one full panel flow | #12410 | 2 |
| #13273 | thin checks for every other panel kind | #12410 | 18 |
Merge #13271, #12409, #12410; the rest are independent of each other.
- One thin spec per panel kind (Bar, Area, Histogram, Heatmap, Pie,
Number, Table, List): it renders, its actions menu offers exactly its
download formats (and Create Alerts where it applies), and a click opens
drilldown or not as the kind declares; a List row opens the log detail
drawer instead. 2 tests each.
- Text keeps its authoring flow: the picker opens the Markdown editor,
and Markdown previews, saves with no query and renders on the grid.
Markdown rendering itself is left to `MarkdownContent.test.tsx` and the
Text story.
- Per review, the per-kind chart and config detail lives in the panel
stories (#13244) and jest, not e2e; the one full panel flow is Time
Series in #13233.
- One commit per kind.
#### Description
Part of the dashboards E2E stack; base #12410. Ports the old dashboards
list spec onto the drivers.
| PR | Content | Base | Tests |
|---|---|---|---:|
| #13271 | uPlot instance on the chart container | `main` | — |
| #12409 | drivers, spec builders; drops the old dashboard specs |
#13271 | — |
| #12410 | panel and editor flows | #12409 | 74 |
| #13263 | dashboards list page | #12410 | 24 |
| #13264 | viewing, sections, edit mode, time range, variables, settings
| #12410 | 67 |
| #13233 | Time Series: the one full panel flow | #12410 | 2 |
| #13273 | thin checks for every other panel kind | #12410 | 18 |
Merge #13271, #12409, #12410; the rest are independent of each other.
- `list/search`, `list/sort`, `list/row-actions`, `list/create-import`,
`list/navigation` (24 tests): search by title, tag and URL query
(case-insensitive via `ILIKE`), every sort and sort params on load, the
row menu (open in new tab, copy link, cancel delete), the New dashboard
dialog and its default name, importing an uploaded JSON file, row click,
side-nav link, Back restoring a typed search.
- `drivers/dashboard-list.ts` grows search, sort, row-menu, dialog and
import handles.
#### Additional Information
Not ported, because the behaviour is gone: Export JSON in the row menu
(removed), and the old "Cancel delete opens the dashboard" bug, which is
fixed — the port asserts the fixed behaviour. Delete-with-confirm lives
in `page/from-scratch` (#12410).
#### Description
Part of the dashboards E2E stack; base #12410.
| PR | Content | Base | Tests |
|---|---|---|---:|
| #13271 | uPlot instance on the chart container | `main` | — |
| #12409 | drivers, spec builders; drops the old dashboard specs |
#13271 | — |
| #12410 | panel and editor flows | #12409 | 74 |
| #13263 | dashboards list page | #12410 | 24 |
| #13264 | viewing, sections, edit mode, time range, variables, settings
| #12410 | 67 |
| #13233 | Time Series: the one full panel flow | #12410 | 2 |
| #13273 | thin checks for every other panel kind | #12410 | 18 |
Merge #13271, #12409, #12410; the rest are independent of each other.
- The one full panel flow: create a Time Series panel from the picker,
pick a metric, run, set an axis bound, a right legend and a label
threshold, save, then check the grid and the saved spec, and again after
a reload.
- Panel time preference: the header pill reads the chosen range.
- The one-row-at-a-time threshold editing is a jest case in
`ThresholdsSection.test.tsx`.
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/4515
#### Description
Part of the dashboards E2E stack; base #12409.
| PR | Content | Base | Tests |
|---|---|---|---:|
| #13271 | uPlot instance on the chart container | `main` | — |
| #12409 | drivers, spec builders; drops the old dashboard specs |
#13271 | — |
| #12410 | panel and editor flows | #12409 | 74 |
| #13263 | dashboards list page | #12410 | 24 |
| #13264 | viewing, sections, edit mode, time range, variables, settings
| #12410 | 67 |
| #13233 | Time Series: the one full panel flow | #12410 | 2 |
| #13273 | thin checks for every other panel kind | #12410 | 18 |
Merge #13271, #12409, #12410; the rest are independent of each other.
- `page/*`: panel data states (no data, error + Retry, loading — once,
on Time Series), extend time range, panel actions incl. CSV download,
grid layout, zoom, drilldown, View modal, creating a dashboard from the
list page.
- `editor/*`: New Panel picker, editor shell, query pane, type
switching, the disabled-kind tooltip, a context link saved in the editor
and shown in the drilldown menu.
- Cases already covered by jest are left to it: picker search and
categories, the context-link dialog, the panel header, NoData actions,
Save gating and the locked actions menu.
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/4524
#### Additional Information
- `view-modal` does not cover "browser Back closes the modal": the modal
rewrites `compositeQuery` into the URL with a fresh id, so Back never
leaves it. Browser Back also doesn't leave the panel editor for the same
reason, so `editor/shell` TC-06 changes route client-side instead
(SigNoz/pulse-pod#623).
#### Description
Base of the dashboards E2E stack.
| PR | Content | Base | Tests |
|---|---|---|---:|
| #13271 | uPlot instance on the chart container | `main` | — |
| #12409 | drivers, spec builders; drops the old dashboard specs |
#13271 | — |
| #12410 | panel and editor flows | #12409 | 74 |
| #13263 | dashboards list page | #12410 | 24 |
| #13264 | viewing, sections, edit mode, time range, variables, settings
| #12410 | 67 |
| #13233 | Time Series: the one full panel flow | #12410 | 2 |
| #13273 | thin checks for every other panel kind | #12410 | 18 |
Merge #13271, #12409, #12410; the rest are independent of each other.
- Driver-based harness (`docs/contributing/tests/e2e.md` → Drivers):
`dashboards` seeds over `POST /api/v2/dashboards` and cleans up
(unlocking first, so a test that locks never leaks its dashboard);
`panels`, `panel-editor`, `panel-config` and `dashboard-list` drive the
UI. `driver()` gains an optional `dispose`.
- Typed spec builders (`helpers/dashboard-spec.ts`, types in
`dashboard-spec.types.ts`) and `query_range` mocks
(`helpers/query-range-mock.ts`).
- Deletes the specs written for the old dashboard
(`tests/dashboards/list.spec.ts`, `details/**`), which target the
rejected `/api/v1/dashboards` and were skipped on `main`, with their
`helpers/dashboards.ts` and testdata JSONs; `routing` now seeds through
the driver. Every behaviour is ported in #13263 and #13264; the few the
product no longer has are listed there.
- Test ids for the panel header and preview pane.
#### Issues closed by this PR
Part of SigNoz/engineering-pod#4304.
#### Additional Information
- `pnpm typecheck` in `tests/e2e` is broken on `main` (`module:
commonjs` + `moduleResolution: bundler`, TS5095); this stack is
typechecked with `--module esnext --lib ES2020,DOM`. Fixing the config
is its own PR.
#### Description
- `UPlotChart` hangs its live uPlot instance on the chart container
(`__uplot`), set on create and removed on destroy. No render path or
uPlot option changes.
- A chart is a canvas, so tests have nothing else to read series, scales
or bands from. The dashboards e2e stack (#12409) and the panel stories
(#13244) both build on this.
2026-10-27 16:22:48 +00:00
2243 changed files with 105842 additions and 47252 deletions
description: Use this agent to convert a SigNoz E2E test plan into Playwright spec files under `tests/e2e/tests/<feature>/`. Examples — <example>Context: A test plan exists and needs to be turned into runnable specs. user: 'Generate the dashboards list specs from the plan in tests/e2e/specs/dashboards-list-test-plan.md' assistant: 'Using the generator agent to drive each scenario in a real browser and write the corresponding Playwright tests.'</example>
description: Use this agent to convert a SigNoz E2E test plan into Playwright spec files under `tests/e2e/tests/<feature>/`, adding or extending the feature's driver under `tests/e2e/drivers/`. Examples — <example>Context: A test plan exists and needs to be turned into runnable specs. user: 'Generate the dashboards list specs from the plan in tests/e2e/specs/dashboards-list/dashboards-list-test-plan.md' assistant: 'Using the generator agent to drive each scenario in a real browser and write the corresponding Playwright tests.'</example>
You are the Playwright Test Generator for the SigNoz frontend. You take a plan written by `playwright-test-planner` and produce runnable Playwright specs that match the conventions documented in [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md). **Read that doc first.** Adhere to it.
You are the Playwright Test Generator for the SigNoz frontend. You take a plan written by `playwright-test-planner` and produce runnable Playwright specs that match the conventions documented in [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) and [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md). **Read both first.** Adhere to them.
# Repo conventions you must follow
- **Spec location:** `tests/e2e/tests/<feature>/<spec-name>.spec.ts`. One file per resource; cross-resource concerns get their own file. Don't repeat the feature name in the filename — the directory already provides it. `dashboards/list.spec.ts`, not `dashboards/dashboards-list.spec.ts`.
- **Auth fixture:** import`test` and `expect`from `'../../fixtures/auth'`, not `@playwright/test`. Specs receive an admin-authenticated page via the `authedPage` fixture (the only user the bootstrap seeds).
- **Spec location:** `tests/e2e/tests/<feature>/<spec-name>.spec.ts`. One file per resource; cross-resource concerns get their own file. Don't repeat the feature name in the filename — the directory already provides it. `dashboards/list/search.spec.ts`, not `dashboards/list/dashboards-list-search.spec.ts`.
- **One import per spec:**`test` and `expect`come from the feature's driver module under `'../../drivers/<feature>'`, never from `@playwright/test` and never from several helper files. Specs receive their feature's driver plus, when they genuinely need it, the admin-authenticated `authedPage`.
```ts
import { test, expect } from '../../fixtures/auth';
import { expect, PATH, test } from '../../drivers/routing';
- **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';
```
- A driver is a function closing over the page that returns `Locator` getters and async actions. Actions wait inside themselves (e.g. `run()` pairs the click with `waitForResponse(/query_range/)`), so specs don't.
- `driver(name, build, dispose?)` in `drivers/kit.ts` makes it a test-scoped fixture on `authedPage`; `dispose` deletes what it seeded. `seeded(name, open, close)` makes a worker-scoped seed.
- Read [tests/e2e/tests/dashboards/editor/query.spec.ts](../../tests/e2e/tests/dashboards/editor/query.spec.ts) and [drivers/panel-editor.ts](../../tests/e2e/drivers/panel-editor.ts) for the canonical shape.
- **Seeds are layers on the feature's `test`.** A spec destructures the seed it needs (`alertList`, `ownedRules`, `resolvedHistory` for alerts) instead of importing a fixtures module. A new seed goes in the feature's driver directory: `seeded()` for read-only data shared by a worker, a test-scoped layer for anything a test mutates.
- **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.
- **Suite-level `beforeAll` + `afterAll` with a `seedIds: Set<string>` registry** — preferred for larger specs. Reduces per-test boilerplate, and one cleanup loop handles every dashboard the suite touched. See [tests/e2e/tests/dashboards/list.spec.ts](../../tests/e2e/tests/dashboards/list.spec.ts) for the canonical shape.
- **Reuse helpers from `tests/e2e/helpers/`.** Don't reinvent. The current set:
- [`helpers/auth.ts`](../../tests/e2e/helpers/auth.ts) — `newAdminContext(browser)` for `beforeAll` / `afterAll` (the `authedPage` fixture is test-scoped and not visible to suite hooks).
- [`helpers/dashboards.ts`](../../tests/e2e/helpers/dashboards.ts) — `authToken`, `gotoDashboardsList`, `createDashboardViaApi`, `importApmMetricsDashboardViaUI`, `deleteDashboardViaApi`, `findDashboardIdByTitle`, `openDashboardActionMenu`, plus the constants used by both helpers and specs (`SEARCH_PLACEHOLDER`, `LIST_HEADING`, `APM_METRICS_TITLE`, `DEFAULT_DASHBOARD_TITLE`).
- **Seed via API when the UI flow is multi-step or brittle.** Implementation lives in `createDashboardViaApi` — use it. `page.request.*` does **not** auto-attach `Authorization`; the helpers handle that for you. The "Enter dashboard name…" inline input on the dashboards list page is a `RequestDashboardBtn` template-feedback form, **not** a create flow — never use it to seed.
- **Reusable JSON fixtures live in [tests/e2e/fixtures/](../../tests/e2e/fixtures/).** `apm-metrics.json` is a real, tag-rich dashboard payload — `importApmMetricsDashboardViaUI(page)` seeds it through the actual Import JSON UI flow.
- **Resource names:** short, descriptive, no timestamps — `dashboards-list-sort-click`, not `Test Dashboard ${Date.now()}`. Each test owns its names; uniqueness comes from cleanup, not disambiguation.
- **Self-contained state.** The bootstrap creates a fresh stack with **zero** dashboards / alerts / etc. Never assume pre-existing data, and scope every assertion to names the spec seeded. Pick the scope per the Seeding table in [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md): test scope (a test-scoped layer, or `try / finally` through the driver) for mutations, worker scope (`seeded()`) for read-only data, suite `beforeAll` for a dataset only one file uses.
- **Drive everything through the feature's driver** in `tests/e2e/drivers/`. A spec that reaches for `page.locator(...)` or `page.getByTestId(...)` is missing a driver method — add the method instead. `onAdminPage(browser, …)` from `drivers/kit.ts` is what suite hooks use, because `authedPage` is test-scoped and not visible to them.
- **Seed through the kit when the UI flow is not under test.** Control-plane objects go through the feature driver (`dashboards.seed`, `alerts.seed.threshold`), which uses `apiDriver(page)` from `drivers/api.ts`; telemetry goes to the seeder. `page.request.*` does **not** carry the token, so never seed with it.
- **Golden telemetry is pre-seeded.** `bootstrap/global.setup.ts` loads logs, traces and metrics over the last 6h (constants in `GOLDEN`, `drivers/dashboard-spec.ts`). Query within ≤ 6h and assert structure (series, rows, request payload), never exact counts.
- **`query_range` mocks** in [`drivers/query-range-mock.ts`](../../tests/e2e/drivers/query-range-mock.ts) are for error / no-data / warning / loading states only; install before `goto`.
- **Reusable payloads live in [tests/e2e/testdata/](../../tests/e2e/testdata/)** (traces and logs POSTed to the seeder, `dashboards/panels.ts`). Infra monitoring reads the integration suite's fixtures through `seedDataset` and asserts on what it returned.
- **Resource names:** a short descriptive prefix plus the worker index, `e2e-alert-list-w${workerIndex}-${Date.now()}`. Six workers share one stack, so a name without the stamp collides.
- **Serial mode** when tests in a file mutate the same list page:
```ts
test.describe.configure({ mode: 'serial' });
```
- **Locator priority** (matches Playwright best practice):
1. `data-testid` (preferred — these are stable, app-author-provided handles)
- **Locate by testid, in the driver.** A missing testid is added to the product source. Role, label and CSS lookups are a stopgap; antd internals (`.ant-select-dropdown`, in `antd.ts`) are the exception.
- **A driver method is navigation or a locator, never a scenario.** A method that exists for one spec belongs in that spec.
- **No `test.slow()` in drivers.** It compounds when called in a loop; use `allowFor(floor)` from `drivers/kit.ts`.
- **Keep driver changes separate from spec changes**, so they land in different commits (see Commits in [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md)).
- **Never commit `test.only`.** CI runs with `forbidOnly: true`.
@@ -64,13 +55,14 @@ For each scenario in the plan:
- For verifications, use the dedicated `browser_verify_*` tools — they capture the assertion as Playwright code in the log.
4. **Read the log.** Call `generator_read_log` immediately after the last step. Don't intersperse other tool calls.
5. **Write the spec.** Call `generator_write_test` with:
- **File path:** `tests/e2e/tests/<feature>/<scenario-slug>.spec.ts` — fs-friendly slug from the scenario title. Drop the feature prefix when it duplicates the directory (`dashboards/list.spec.ts`, not `dashboards/dashboards-list.spec.ts`).
- **File path:** `tests/e2e/tests/<feature>/<scenario-slug>.spec.ts` — fs-friendly slug from the scenario title. Drop the feature prefix when it duplicates the directory (`dashboards/list/search.spec.ts`, not `dashboards/list/dashboards-list-search.spec.ts`).
- **Single test per file** if the planner specified one-test-per-file; otherwise group related tests into one file with a shared `test.describe('<Feature>', () => { … })`.
- **`describe` block** matches the top-level plan section.
- **Comments only where the WHY is non-obvious** — section dividers between TC groups, hidden constraints, gotchas the reader can't infer from the code (e.g. "Monaco swallows Escape — click the title to blur first"). **Do not narrate steps** by pasting the plan's bullets back as `// 1. Navigate…` `// 2. Verify…` comments — the helper / locator names already say what each line does, and the duplication is bloat. If a step's intent isn't clear from the code, rename the helper or extract a variable rather than reaching for a comment.
- **Imports** from `../../fixtures/auth`. **Do not** import from `@playwright/test` directly.
- **Try / finally** cleanup using the API (delete the resources you seeded).
- **Imports** from `../../drivers/<feature>`. **Do not** import from `@playwright/test` directly, and do not import a second helper module.
- **Cleanup** comes from the seed layer or the driver's `dispose`. Anything a test creates inline is removed in `try / finally` through the driver.
- **Move recorded locators and waits into the driver.** The log gives raw `page.*` calls; the spec should read as driver actions.
# Quality bar — what to write, what to skip
@@ -88,81 +80,57 @@ When you cut, merge, or renumber TCs vs the plan, note it in your final summary.
For a plan section:
```markdown
### 1. Page Load
### 1. Search
#### TC-01 page chrome and core controls render
#### TC-01 searching by name writes the query to the URL and lists the match
**Preconditions:** one dashboard with a unique name (seed via the `dashboards` driver).
**Steps:**
1. Navigate to `/dashboard`
2. Verify the page title is "SigNoz | All Dashboards"
3. Verify the heading "Dashboards" is visible
**Cleanup:** delete the seeded dashboard via API.
1. Open the dashboards list.
2. Search `name CONTAINS '<name>'`.
**Expected:** the `query` URL param carries the search; only the seeded dashboard is listed.
```
You produce (suite-level shape, preferred for files with multiple scenarios):
You produce:
```ts
// tests/e2e/tests/dashboards/list.spec.ts
import type { Page } from '@playwright/test';
import { expect, test } from '../../fixtures/auth';
import { newAdminContext } from '../../helpers/auth';
// tests/e2e/tests/dashboards/list/search.spec.ts
import {
authToken,
createDashboardViaApi,
deleteDashboardViaApi,
gotoDashboardsList,
} from '../../helpers/dashboards';
expect,
listDashboard,
nameContains,
test,
uniqueName,
} from '../../../drivers/dashboard-list';
test.describe.configure({ mode: 'serial' });
const seedIds = new Set<string>();
async function seed(page: Page, title: string): Promise<string> {
const id = await createDashboardViaApi(page, title);
Note how the example carries no `// 1. …` `// 2. …` step narration — the helper and locator names already say what each line does. The only comments worth adding are ones a reader couldn't recover from the code itself.
Note: no cleanup code (the `dashboards` driver deletes its seed), no raw locators (they live in `drivers/dashboard-list.ts`), and no `// 1. …` step narration. If a step needs an element the driver does not expose, add a method to the driver; do not reach for `page.getByTestId(...)` in the spec. The only comments worth adding are ones a reader couldn't recover from the code itself.
# Known UI gotchas (apply when relevant)
- **Ant Popover positioning vs viewport.** Items inside a Popover — for example the "Delete dashboard" entry inside the row action menu — can render outside the viewport in headless CI even when scrolled. `click({ force: true })` skips actionability checks but Playwright still requires the click coordinates to land inside the viewport. Use `dispatchEvent('click')` instead — it fires the click directly on the DOM node, React's onClick still runs, and there are no coordinate checks. Reach for it whenever a CI failure complains about "Element is outside of the viewport" on a popover/tooltip option.
- **Sticky-header rows below the fold.** When the table accumulates rows, the search-filtered row's `dashboard-action-icon` can land below a sticky header. Always `await actionIcon.scrollIntoViewIfNeeded()` before clicking. The `openDashboardActionMenu` helper already does this — use it instead of clicking the icon directly.
- **Sticky-header rows below the fold.** When the table accumulates rows, the search-filtered row's `dashboard-action-icon` can land below a sticky header. Always `await actionIcon.scrollIntoViewIfNeeded()` before clicking, inside the driver action.
- **React Query mutations vs navigation.** UI delete clicks fire an async DELETE through React Query. Navigating away before the mutation completes cancels it. Pair the click with `page.waitForResponse((r) => r.request().method() === 'DELETE' && /\/dashboards\//.test(r.url()))` and `await expect(dialog).not.toBeVisible()` before the next `page.goto(...)`.
- **Monaco editor swallows Escape.** Inside the Import JSON dialog the Monaco editor grabs focus and intercepts the Escape keystroke. Click the modal title (or any non-editor element inside the dialog) first to blur Monaco; Ant's `keyboard` handler then sees the Escape and dismisses.
- **Empty zero-state hides controls.** With no dashboards in the workspace, the search input, sort button, "All Dashboards" header, and `new-dashboard-cta` testid are absent — only the page heading and the inline "request a template" form render. Always seed at least one dashboard before driving any test that touches list-page controls.
- **Monaco editor swallows Escape.** Inside the Import JSON dialog the Monaco editor grabs focus and intercepts the Escape keystroke. Click the modal title first to blur Monaco; `dashboardList.dismissWithEscape()` does this.
- **Empty zero-state hides controls.** With no dashboards in the workspace, the list controls and `new-dashboard-cta` are absent. Always seed at least one dashboard before driving any test that touches list-page controls.
description: Use this agent to debug and fix failing SigNoz E2E Playwright tests. Examples — <example>Context: A spec is red. user: 'tests/e2e/tests/dashboards/list.spec.ts is failing, fix it' assistant: 'Using the healer agent to debug each failing scenario and adjust the spec.'</example> <example>Context: After a frontend change a previously-green spec broke. user: 'TC-09 in alerts started failing' assistant: 'Launching the healer to investigate.'</example>
description: Use this agent to debug and fix failing SigNoz E2E Playwright tests. Examples — <example>Context: A spec is red. user: 'tests/e2e/tests/dashboards/list/search.spec.ts is failing, fix it' assistant: 'Using the healer agent to debug each failing scenario and adjust the spec.'</example> <example>Context: After a frontend change a previously-green spec broke. user: 'TC-09 in alerts started failing' assistant: 'Launching the healer to investigate.'</example>
You are the Playwright Test Healer for the SigNoz E2E suite. You debug and fix red specs with a methodical approach. Read [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) before you start — it documents the harness and the conventions you must preserve.
You are the Playwright Test Healer for the SigNoz E2E suite. You debug and fix red specs with a methodical approach. Read [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) and [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md) before you start — they document the harness and the conventions you must preserve.
# Preconditions
@@ -29,9 +29,9 @@ Don't try to start the stack yourself — it can take ~4 minutes on a cold build
4.**Root-cause.** Distinguish between:
- **Selector drift** — the app changed `data-testid` or text. Fix the locator. Prefer `data-testid` (grep `frontend/src/<feature-dir>/` for the new one).
- **Timing** — the test races a load. Replace `waitForTimeout` with `await expect(locator).toBe…()` or `page.waitForResponse(...)` on the triggering action.
- **State leak** — a previous test left data behind, or this test assumes data the bootstrap doesn't seed. Ensure the test seeds via API and cleans up in `try / finally`. The bootstrap creates a fresh stack with **zero** dashboards / alerts.
- **State leak** — a previous test left data behind, or this test assumes data the bootstrap doesn't seed. Ensure the test seeds through its seed layer or driver (a driver's `dispose` cleans up), scopes its assertions to the names it seeded (with the worker index), and cleans up. The bootstrap creates a fresh stack with **zero** dashboards / alerts and only the golden telemetry; parallel per-test seeds share the tenant, so an exact-count assertion on telemetry is a test bug. Assert structure instead.
- **Genuine app bug** — the app is broken, not the test. Mark the test with `test.fixme(...)` and add a one-line `// known: <description>` comment. Don't silently change the assertion to make it pass.
5.**Fix.** Edit the spec. Preserve TC-NN titles, the `authedPage` fixture,`try / finally` cleanup, and serial mode if present. If you renumber, retitle, or `test.fixme(...)` any TC, flag it in your final summary so the user can re-run the planner — the plan and the QA checklist (`tests/e2e/specs/<feature>/checklists/<feature>-functional-checklist.md`) re-derive from the current `.spec.ts` and will otherwise drift.
5.**Fix.** Edit the driver first, the spec second. A driver change must keep every other spec importing it green, so run them too. Preserve TC-NN titles, the feature's`test` import, the spec's cleanup, and serial mode if present. If you renumber, retitle, or `test.fixme(...)` any TC, flag it in your final summary so the user can re-run the planner — the plan and the QA checklist (`tests/e2e/specs/<feature>/checklists/<feature>-functional-checklist.md`) re-derive from the current `.spec.ts` and will otherwise drift.
6.**Re-run only the fixed test** before moving to the next failure. Three options:
-`npx playwright test -g 'TC-09' --project=chromium` — target a single TC by title
-`npx playwright test --last-failed --project=chromium` — replay everything that failed last run
@@ -41,18 +41,20 @@ Don't try to start the stack yourself — it can take ~4 minutes on a cold build
# Repo-specific signals
- **Reuse helpers before adding new code.** [`tests/e2e/helpers/dashboards.ts`](../../tests/e2e/helpers/dashboards.ts) and [`tests/e2e/helpers/auth.ts`](../../tests/e2e/helpers/auth.ts) already export the API-seed, cleanup, navigation, and action-menu helpers most fixes need. Prefer importing from there over re-inlining auth/login/POST plumbing in the spec.
- **Fix it in the driver, not in the spec.** Selector drift belongs in `tests/e2e/drivers/<feature>` — one edit there fixes every spec that hit the same element. A `page.locator(...)` added to a spec body is a regression, not a fix.
- **Ant Popover items can fail with "Element is outside of the viewport" — even with `force: true`.** `force` skips actionability checks but Playwright still requires click coordinates to land in the viewport when it dispatches the synthetic mouse event. The robust fix is `tooltip.getByText('…').dispatchEvent('click')` — fires the click directly on the DOM node, React's `onClick` runs, and no coordinate calculation happens. Apply this whenever the failure log mentions "outside of the viewport" on a popover/tooltip option, especially in CI where layout differs subtly from local.
- **Action-icon rows below the fold.** With multiple seeded dashboards, a search-filtered row can scroll behind a sticky table header. The `openDashboardActionMenu` helper does `scrollIntoViewIfNeeded` already — if a test still drives the icon directly, fix it to use the helper or add the scroll.
- **Action-icon rows below the fold.** With multiple seeded dashboards, a search-filtered row can scroll behind a sticky table header. Add `scrollIntoViewIfNeeded` before the click, in the driver action (`dashboardList.openRowMenu(name)`).
- **React Query mutations vs page.goto.** UI delete clicks call `mutate()` asynchronously; if the test navigates away before the response lands, the mutation is cancelled and the dashboard is *not* deleted. Wait for the DELETE response and the dialog dismissal explicitly: `page.waitForResponse((r) => r.request().method() === 'DELETE' && /\/dashboards\//.test(r.url()))` plus `await expect(dialog).not.toBeVisible()`.
- **Monaco editor swallows Escape inside the Import JSON dialog.** If a test that presses Escape times out, click the modal title (or any non-editor element inside the dialog) first to blur Monaco, then press Escape.
- **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.
- **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 `dashboards.seed(...)`.
- **Auth.** `tests/e2e/drivers/auth.ts` logs in once per worker and caches `storageState` (localStorage with `AUTH_TOKEN`). For API-driven seeding and cleanup use `apiDriver(page)` from `drivers/api.ts`, which attaches the header for you. Never re-implement login.
- **Seeds.** Feature seeds are layers on the feature's `test`, e.g. `alertList`, `ownedRules` and `resolvedHistory` in `drivers/alerts/seeds.ts`. When a test fails on missing data, check which seed it destructures and whether it scopes to that seed's names.
- **Timeouts.** Never add `test.slow()` to a driver: it triples the remaining budget per call and compounds in loops. Use `allowFor(floor)` from `drivers/kit.ts`. Budgets and their measurements are in `tests/e2e/docs/flake-notes.md`.
- **CI-only failures.** Reproduce with `STRESS=1 CI=1 pnpm test <path>` from `tests/e2e/` before calling it a product bug.
- **Commits.** Driver fixes and spec fixes go in separate commits (see Commits in `tests/e2e/AGENTS.md`).
- **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.
- **Type-check.** After edits, run `pnpm typecheck` and`pnpm lint`from `tests/e2e/`.
You are an expert E2E test planner for the SigNoz frontend, working inside the SigNoz monorepo. Your test plans drive Playwright specs that run against the local pytest-bootstrapped backend. Read [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) before planning — it documents the harness, the `authedPage` fixture, the TC-NN naming convention, and the self-contained-state principle that every plan you write must respect.
You are an expert E2E test planner for the SigNoz frontend, working inside the SigNoz monorepo. Your test plans drive Playwright specs that run against the local pytest-bootstrapped backend. Read [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) and [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md) before planning — they document the harness, the driver kit, the TC-NN naming convention, the four-layer rule, and the self-contained-state principle that every plan you write must respect.
You will:
@@ -16,35 +16,71 @@ You will:
- Interactive elements and their `data-testid` attributes (preferred locators)
- URL query-param state (search, sort, pagination)
- API endpoints the UI calls — these inform what cleanup endpoints exist for `try/finally` teardown
-The frontend stores its JWT in `localStorage` under `AUTH_TOKEN`and the API requires `Authorization: Bearer <token>` for protected endpoints. Plans that need API-driven seeding should note this so the generator can use `page.request.*`.
- API endpoints the UI calls — these tell the generator which seed and cleanup calls the driver needs
-Seeding goes through the feature driver (`apiDriver`underneath) for control-plane objects and through the seeder for telemetry, never through `page.request.*`. Name the seed each scenario needs and its scope: test for anything it mutates, worker for read-only data.
2.**Check what's already wired up.**
- [tests/e2e/helpers/dashboards.ts](../../tests/e2e/helpers/dashboards.ts) and [tests/e2e/helpers/auth.ts](../../tests/e2e/helpers/auth.ts) hold reusable helpers (`createDashboardViaApi`, `gotoDashboardsList`, `openDashboardActionMenu`, `newAdminContext`, `importApmMetricsDashboardViaUI`, etc.). When the plan touches dashboards, reference these by name in the steps so the generator can reuse them rather than reinvent.
- [tests/e2e/fixtures/apm-metrics.json](../../tests/e2e/fixtures/apm-metrics.json) is a real-world dashboard payload (rich tags, panels, description) suitable as a seed fixture — note in the plan if a scenario benefits from it.
2.**Work out which layer each scenario belongs to — before planning it.**
3.**Navigate and explore**
This is the step that keeps the suite small, and it has to happen here
because by review time the spec already exists. Read
[tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md) for the full rule. For every
candidate scenario, in order:
- **jest** — a pure function, a reducer, a config literal, a component's own
behaviour. Grep `frontend/src/**/__tests__/` for the container first; most
are covered far better than a browser could.
- **Storybook + msw** — a rendered state that needs no backend (empty, error,
loading).
- **pytest integration** — anything the API does: ordering, pagination,
filter semantics, validation, roll-ups, status codes. **Grep
`tests/integration/` and name the test you found.** For infra monitoring
(`tests/integration/tests/inframonitoring/`, 123 test functions) and for
alert evaluation (`tests/integration/tests/alerts/`) the answer is usually
that it already exists.
- **E2E** — only when a real response has to reach a real DOM: URL and
history, a write path that must persist, cross-page navigation, one seeded
proof per surface.
The plan must say, per scenario, why it is not one of the cheaper three. A
scenario that cannot answer that does not go in the plan.
3.**Check what's already wired up.**
- [tests/e2e/drivers/](../../tests/e2e/drivers/) holds one driver module per
feature plus the shared ones (`tanstack`, `querysearch`, `antd`, `api`,
`storage`, `console`). Reference driver methods by name in the steps so the
generator reuses them rather than reinventing a locator.
- If a step needs something the driver does not expose, say so — the
generator adds a driver method, not a `page.locator` in the spec.
- **Telemetry is pre-seeded.** `bootstrap/global.setup.ts` loads a golden
OTel-demo dataset (logs, traces, metrics over the last 6h; constants in
`GOLDEN` in `drivers/dashboard-spec.ts`). Plans that query data use a
window ≤ 6h and assert structure, not exact counts.
(`dashboards/panels.ts`, `traces/large-trace.json`); note in the plan if a
scenario benefits from one.
4.**Navigate and explore**
- Invoke `planner_setup_page` once before any other browser tool.
- Use `browser_snapshot` to read the page's accessibility tree. **Do not take screenshots unless absolutely necessary** — snapshots are cheaper and more legible.
- Edge cases and boundary conditions (empty state, single item, > pagination threshold)
- Error handling and validation
- URL state and deep-linking
- Cross-flow regressions (e.g. searching while paginated)
5.**Structure the test plan**
6.**Structure the test plan**
Each scenario must include:
- **TC-NN** title — `TC-NN <short description>` (matches the naming this repo uses for test titles).
- Preconditions (what state the test expects — note that the bootstrap creates a fresh stack with **zero dashboards / alerts / etc.**, so plans must seed their own data).
- Preconditions (what state the test expects — note that the bootstrap creates a fresh stack with **zero dashboards / alerts / etc.** (only the golden telemetry), so plans must seed their own resources).
- Step-by-step user actions.
- Expected outcomes per step.
- Cleanup notes (what gets created and how to remove it — usually via API).
6.**Save the plan and the checklist**
7.**Save the plan and the checklist**
- **Plan:** `tests/e2e/specs/<feature>/<feature>-test-plan.md`. **`tests/e2e/specs/` is gitignored** — plans are scratch artifacts: working input for the generator, regenerable, not committed. Don't treat them as durable documentation. The committed tests are the source of truth.
- **Checklist:** `tests/e2e/specs/<feature>/checklists/<feature>-functional-checklist.md`. A manual-verification runbook that mirrors the TC list one-to-one (one checkbox per TC) for QA hand-off. Same gitignore, same scratch status.
- **The checklist must stay in sync with the TCs.** When you regenerate the plan, regenerate the checklist alongside it — they share TC IDs and titles, and the checklist ordering must match. If the existing spec under `tests/e2e/tests/<feature>/` has more / fewer / different TCs than the prior plan, the spec is authoritative: re-derive plan and checklist from it.
@@ -64,7 +100,7 @@ The dashboards list page (`/dashboard`) lists all dashboards in the workspace. F
- Open per-row actions: View, Open in New Tab, Copy Link, Export JSON, Delete dashboard
- Create a new dashboard via the "New dashboard" dropdown (Create dashboard / Import JSON / View templates)
**Bootstrap state:** the pytest harness creates a fresh stack with no pre-seeded dashboards. Every test must seed its own data. The "Enter dashboard name…" inline input is a "request a new template" feedback form — **not** a create flow. The only UI create path is the dropdown.
**Bootstrap state:** the pytest harness creates a fresh stack with no pre-seeded dashboards. Every test seeds its own through the `dashboards` driver (`dashboards.seed(...)`, deleted when the test ends).
**Known limitations:**
- Ascending sort is not yet implemented — repeated clicks on the sort button keep `order=descend`.
@@ -87,7 +123,7 @@ The dashboards list page (`/dashboard`) lists all dashboards in the workspace. F
- All Dashboards section header rendered.
- Search input, sort button, and at least one dashboard thumbnail visible.
**Cleanup:**delete the seeded dashboard via `DELETE /api/v1/dashboards/<id>`.
**Cleanup:**none in the spec — the `dashboards` driver deletes what it seeded.
#### TC-02 ...
</example-spec>
@@ -95,8 +131,8 @@ The dashboards list page (`/dashboard`) lists all dashboards in the workspace. F
**Quality bar:**
- Steps must be specific enough that any tester (or the generator agent) can follow without ambiguity.
- Each scenario must own its preconditions and cleanup. **Do not invent cross-file global fixtures** — they break parallel-by-file execution. Suite-level `beforeAll` / `afterAll`*within* a single spec file is fine and is the preferred shape for files with > ~10 scenarios; per-test `try / finally` is fine for smaller specs.
-Prefer stable `data-testid`attributes when noting locators; fall back to ARIA roles or accessible names; treat CSS selectors as last resort.
- Each scenario must own its preconditions and cleanup, and assert only on names it seeded. **Never depend on data another spec file created.** Files run in parallel. Pick the seed scope from the Seeding table in [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md); a driver's self-cleaning seed (`dispose`) or a worker-scoped `seeded(...)` layer covers most cases.
-Note the `data-testid`for every element a step touches. Where none exists, say so: the generator adds it to the product rather than falling back to roles or CSS (antd internals aside).
- **Don't pad coverage.** Every TC must catch a regression that would actually ship if the test were missing — a real branch in the source, a real user-visible failure mode. A TC that asserts a hard-coded string is still rendered, or that a button is still a button, adds nothing but maintenance cost: it inflates the suite, slows CI, and trains readers to skim past the directory. Before writing a scenario, ask "what code change would break this?" — if the only answer is "deleting the literal under test," cut it or fold it into a richer scenario as one assertion among many.
- **Collapse near-duplicates.** Two TCs that differ only in input value (search by title vs search by description, when the underlying code path is the same) should merge into one parameterised scenario unless each input genuinely exercises a distinct branch. Prefer one assertion-rich TC over three thin ones.
- **Smoke-checks aren't TCs.** "Heading is visible" belongs as the first assertion inside a real scenario, not as its own numbered case.
@@ -4,9 +4,9 @@ Applies to everything in the repo — code, config, workflows.
- **No unnecessary comments.** Do not comment where the code is self-explanatory; never restate what the code already says.
- **Document only** non-obvious behavior, constraints, formats, and edge cases.
- **Rationale goes in prose, not source.** Why a version is pinned, why a job exists, how a subsystem fits together — that belongs in the README or the PR.
- **Never remove pre-existing comments** when editing code. The bar above applies to comments you write, not comments already there.
- **Never talk to the reviewer.** No comments about where a change came from, what was changed, or why the change is correct — that belongs in the PR description and is noise the moment it merges.
- **Rationale goes in prose, not source.** Why a job exists, how a subsystem fits together: that belongs in the README or the PR. Exception: a config entry that would look like a mistake without its reason (a pinned version, a trust or lint exclusion) keeps a short reason next to it.
- **Never remove pre-existing comments** when editing code. The bar above applies to comments you write, not comments already there. When the code a comment describes changes, update the comment; when that code is deleted, delete the comment with it.
- **Never talk to the reviewer.** No comments about where a change came from, what was changed, or why the change is correct — that belongs in the PR description and is noise the moment it merges. A banner a skill requires (the AI-owned banner on story mocks and `tests/e2e/drivers/`) tells maintainers how the file is made; keep it.
- **Less is more.** When writing something intended for human consumption, (comment, commit message, reply to prompt) use as few words as possible. Pick every word meticulously to reduce the volume to a strict minimum. Be down to the point. Less is more.
Language rules build on this one: [`go-comments`](go-comments.md), [`py-comments`](py-comments.md).
Pick the store by what the state is. Never add Redux or React Context for shared state; to move existing Redux or Context state, use the `migrate-state-management` skill ([`frontend/.claude/skills/migrate-state-management/SKILL.md`](../../frontend/.claude/skills/migrate-state-management/SKILL.md)).
| State | Use | Never |
|---|---|---|
| Server / API data | React Query, generated hooks from `src/api/generated` | a copy in Redux, Context or Zustand |
| Shareable: filters, time range, selection, page | nuqs (`useQueryState` with a parser) | Redux, Context, hand-parsed `useSearchParams` |
| Global client state | a small Zustand store per domain | Redux, Context |
| One component's UI state | `useState` / `useReducer` | any store |
## Server state
- Expose a query through a hook that returns its result plus memoized derived values. Never dispatch a response into another store.
- **The query key holds every value the request reads.** A field change that alters the request (name, context, data type) must change the key; a lossy signature serves stale rows.
## URL state
- Keep the URL small (Chrome trims near 2k characters). No large datasets, no sensitive data.
- **A rewrite touches only the params it owns.** Repairing or normalising one param keeps the rest (`activeLogId`, pagination) as they were.
- **Links carry the current time range.** A row link, "open in new tab" and "copy link" land on the same period as an in-app click.
- **A preset read from the URL applies once.** A later user change or reset must not re-apply it.
## Zustand
- One `create()` per module; split a large store into slice factories combined in that one `create()`.
- Always read through a selector: `useDashboardStore((s) => s.isDashboardLocked)`, never `useDashboardStore()`.
- Update only through `set`; never mutate state. State fields before actions in the store object.
- Hooks never call `useStore.getState()` (`signoz/no-zustand-getstate-in-hooks`); export standalone actions instead.
## Local and form state
- **Editable state starts from saved data once.** Initialise the form from the saved value, then let the user own it; never re-derive it from saved data on each render.
- **A mode switch owns its in-flight work.** While a request from one mode runs, disable the switch or drop the result when the mode changes, so it can't close or overwrite the new mode.
Examples and the harness API live in [`frontend/src/tests/README.md`](../../frontend/src/tests/README.md).
- **Import from the harness.** `render`, `screen`, `userEvent`, `waitFor` and the rest come from `tests/test-utils`, never from `@testing-library/react`. The harness mounts the router, React Query, Redux, app context and preferences. When it can't do what a test needs, extend `tests/test-utils` instead of importing around it.
- **Find elements by test ID.** Query with `getByTestId`/`findByTestId` on the `data-testid` or `testId` the component exposes, and add one to the component when it has none. Fall back to role or text only for markup the app doesn't own. A test ID matches exactly one element and survives copy and markup changes; `getByRole` computes the accessibility tree on every call and is the slowest query.
- **Interact like a user.** `const user = userEvent.setup({ pointerEventsCheck: 0 })`, then an awaited `user.click`, `hover`, `keyboard` or `type`. `fireEvent` only for events `userEvent` has no equivalent for: scroll, resize, setting `scrollTop`.
- **Fake timers per test only**, for debounce or throttle, with `userEvent.setup({ advanceTimers: (ms) => jest.advanceTimersByTime(ms) })`. Never global.
- **Network through MSW.** The global server runs for every test; override per test with `server.use(...)` from `mocks-server/server`. Large fixtures go in `mocks-server/__mockdata__`.
- **Router through the harness.** Pass `initialRoute`/`routePath` to `render` and assert with `getCurrentLocation()` from `lib/router/navigation`. Never mock `react-router`, `react-router-dom` or `lib/history`; when a test can't render at a route, use the `lib/router` spies in `tests/router-mocks`.
- **Mock only business boundaries.** Local `jest.mock` for API modules, custom hooks and domain components. Never re-mock what the harness provides or what `jest.config.ts` maps (`uplot`, `@signozhq/icons`, `useSafeNavigate`, `logEvent`).
- **Type mocks and fixtures.** `jest.mocked(fn)` for module mocks, `jest.fn() as jest.MockedFunction<T>` for a standalone mock you configure or assert on. Fixtures carry their real type, from `src/api/generated` where it exists. No `any`, no `as unknown as`. An inline `onX={jest.fn()}` on a typed prop that is never asserted needs no annotation.
- **A test fails without its change.** Revert the change, or break the line under test, and run the test before you finish: it must fail. Assert the specific outcome (exact text, request payload, call arguments), never only that an element exists.
- **No limit on tests per file.** Split a file when it mixes unrelated behaviour, not to hit a count.
`ILog.body` is `string | ILogBody`. On `use_json_body` tenants the API sends an object, and the collector wraps a plain-string body into `{ message }`. Every read goes through `frontend/src/lib/logs/logBody.ts`.
- **Read with a getter, never directly.** `getBodyDisplay` for a list cell or a log line, `getBodyMessage` for the drawer header. Both accept `undefined` and collapse a `{message}`-only object to its message.
- **Never cast or stringify a body inline.** No `as string`, no `String(log.body)`, no `JSON.stringify(log.body)`, no `typeof body === 'string' ? … : …` outside the module. The v5 converter collapses `{ message }` once with `extractOnlyMessageBody`; live logs and the dashboard list panel skip the converter, so their rows carry the raw object and only a getter renders them right.
- **Resolve a selected field with `getLogFieldValue`** from `lib/logs/flatLogData.ts`, so a `body.<key>` column works on both string and object bodies. An object value renders as JSON and is not offered as an add-to-query filter.
- **The drawer tree and field lookup are the only places that use the raw value.** Everything else renders a string from a getter.
- **Review check.** A raw body value used outside a getter call, `extractOnlyMessageBody` in the converter, `flatLogData.ts` and `LogDetailedView/utils.tsx` is a finding.
- **Keep the commit body short and human readable** focused on decision made if any. Commit body must not re-iterate the changes done, skip if title is sufficient in conveying the change.
- **Use convensional commit format** for commits and PR title.
- **Do not amend the commits once pushed.** Always create a new commit once changes are pushed to remote.
- **Review fixes are new commits too.** A force push hides what changed from human reviewers and makes Greptile post its findings again. Bring in `main` with a merge; only a stacked branch (`gh stack`) rebases, since the stack moves as a unit.
- **Answer every Greptile thread.** Reply with the commit that fixes it (`Fixed in 4df6f3198d: <what changed>`) or the reason it stays, then resolve. Never resolve one with neither: Greptile withdraws a finding, and remembers it, only when told why.
- **Leave human review threads to the author.** Fix the code they ask for, but never reply to or resolve a human reviewer's thread.
@@ -10,6 +10,9 @@ Page stories are written with the `signoz-page-story` skill ([`.claude/skills/si
- **A UI change lands with its story.** Any change that alters what a page renders — a new page, a new state, a new branch, a changed layout — updates the story of every page that renders it, or creates one where the page has none. Go through the `signoz-page-story` skill; never hand-write a page story outside it.
- **New page, new story. New state, new control or variant.** The skill decides which: a control on a response, a variant worth linking to, or a `play` story for a state only a click reaches.
- **A shared component is covered through its pages.** A change under `components/` or `container/` with no story of its own updates the stories of the pages that render it.
- **Plays live with the mocks.** A `play` is a named function exported from `stories/__story_mockdata__/<page>.play.ts`, which is collapsed for review like the rest of the AI-owned mocks. The story file only names it: `play: openEditDrawer`. A story file that still holds play bodies moves them there the first time you edit it.
- **A play passes for every control value.** Branch each check on the args it depends on (panel kind, data state, row count, hidden header). A value the page supports must never fail the play.
- **Mocks answer every request a reachable state makes.** Selecting a row, opening a view or switching a tab that fetches more hits a handler, never the 501 catch-all.
- **Already covered, say so.** When the existing story and its controls already reach the changed state, no story edit is needed; state that in the PR description.
- **Opt out only when there is no visual diff.** Skip stories only when the change renders nothing differently: types, api clients, hooks with no UI branch, tests. Deleting a page deletes its stories.
- **Prove it with a visual diff when asked.** Before/after screenshots and "did this touch other screens" go through `sbshot-visual-diff`, which can only show what a story renders.
- **A variant earns a story only when it is worth linking to**: an empty workspace, a viewer, a page mid-load. Everything else stays a control.
- **Endpoints the page owns go through `response.json`**, so the Data control covers loaded, loading and failed in one declaration. Endpoints the page cannot render without (ingestion detection, preferences, feature payloads) take a plain resolver so the shell survives the loading and error states.
- **Query-param state starts from `route`** (`/logs?tab=explorer`). In-page param navigation works inside a story; a different pathname is blocked and reported by the overlay. A control for a param is worth it only when the param is a page mode someone would want to flip.
- **File layout**: every story file for a page lives under `src/pages/<Page>/stories/`: `<Page>.stories.tsx`, `<Page>.stories.mocks.tsx`, payload builders in `stories/__story_mockdata__/<page>.ts`. Nothing page-specific in `src/storybook/controls/`. A page that is a tab strip over several routes gets one story file per tab, in its own folder under the module page (`LogsModulePage/Pipelines/stories/Pipelines.stories.tsx`), each with its own mocks and `__story_mockdata__/`; the builders more than one tab needs stay in the module page's own `stories/__story_mockdata__/` (`AlertList/stories/__story_mockdata__/alerts.ts`), which a tab reaches as `../../stories/__story_mockdata__/alerts`. Every one of them renders the module page, so the tab strip is there, and the `route` its mocks return decides which tab is open. A page's permission stories go one level further down, in `stories/authz/<Page>.authz.stories.tsx`, on the page's own mocks: see **Permission stories**.
- **A state only a click reaches is a story with a `play` function**, not a control: a drawer, a modal, an edit mode the page holds in component state. Drive it with `userEvent` and the queries from `storybook/test`, take the first of a repeated row action, and wait on the state's own text. The page fetches before it renders a row, so the finder needs a timeout past the 1s default. A state the app drops again on its own, such as one keyed on an array identity that a refetch replaces, does not get a story: it would not survive being looked at. A _sequence_ of such states, a wizard's steps or a questionnaire's pages, is still a control: declare the steps in the mocks module and walk them from a `play` on the meta that destructures `mount`, which is what makes Storybook replay it on an arg change. See [references/controls.md](references/controls.md).
- **File layout**: every story file for a page lives under `src/pages/<Page>/stories/`: `<Page>.stories.tsx`, `<Page>.stories.mocks.tsx`, payload builders in `stories/__story_mockdata__/<page>.ts`, play functions in `stories/__story_mockdata__/<page>.play.ts`. Nothing page-specific in `src/storybook/controls/`. A page that is a tab strip over several routes gets one story file per tab, in its own folder under the module page (`LogsModulePage/Pipelines/stories/Pipelines.stories.tsx`), each with its own mocks and `__story_mockdata__/`; the builders more than one tab needs stay in the module page's own `stories/__story_mockdata__/` (`AlertList/stories/__story_mockdata__/alerts.ts`), which a tab reaches as `../../stories/__story_mockdata__/alerts`. Every one of them renders the module page, so the tab strip is there, and the `route` its mocks return decides which tab is open. A page's permission stories go one level further down, in `stories/authz/<Page>.authz.stories.tsx`, on the page's own mocks: see **Permission stories**.
- **A state only a click reaches is a story with a `play` function**, not a control: a drawer, a modal, an edit mode the page holds in component state. Drive it with `userEvent` and the queries from `storybook/test`, take the first of a repeated row action, and wait on the state's own text. The page fetches before it renders a row, so the finder needs a timeout past the 1s default. The play is a named function exported from `__story_mockdata__/<page>.play.ts` and the story file only names it (`play: openEditDrawer`), so the reviewer reads states, not clicks; a story file that still holds play bodies moves them there the first time you edit it. It branches every check on the args it depends on: a control value the page supports never fails it. A state the app drops again on its own, such as one keyed on an array identity that a refetch replaces, does not get a story: it would not survive being looked at. A _sequence_ of such states, a wizard's steps or a questionnaire's pages, is still a control: declare the steps in the mocks module and walk them from a `play` on the meta that destructures `mount`, which is what makes Storybook replay it on an arg change. See [references/controls.md](references/controls.md).
- **The mocks are AI-owned and say so.** `<Page>.stories.mocks.tsx` and every file under a `__story_mockdata__/` open with this banner, above the imports:
- [ ] a page tagged `authz` has its `Authz` folder: one story per permission it reads, each reached by `revoked`, none of them a role preset, and each one checked by the `disabled` state of what the permission gates
- [ ] the mocks module and every `__story_mockdata__` file carry the AI-owned banner
- [ ] every control flipped once, its effect seen on screen
- [ ] every play passes with each control value its story exposes, and the story file holds no play body
- [ ] every request a reachable state makes (a selected row, an opened view, a switched tab) has a handler
- [ ] console clean: no `[storybook] no msw handler`, no 501, no msw unhandled request, no React warning
- [ ] no navigation overlay on mount
- [ ] `pnpm tsgo --noEmit`, `pnpm exec oxlint <files>`, `pnpm exec oxfmt --check <files>` all clean. The repo has no prettier: `pnpm exec prettier` prints a pass while exiting 254
@@ -14,7 +14,7 @@ Grep recipes, run against the page's directories:
| endpoint URLs | the api module's `axios.get\|post` |
| endpoint URLs behind a generated hook | the hook lives in `src/api/generated/services/<name>/index.ts` and the URL only appears in the fetcher body: `rg 'url: \`' src/api/generated/services/<name>/` |
| url state | `useUrlQuery\|useUrlQueryData\|useUrlSearchState\|useQueryState\|QueryParams\.` |
The two-step version is only justified when `ConfigAdapter` has multiple distinct consumers that use it in different ways.
@@ -66,10 +66,10 @@ A method on a structured type should not strip meaning from the structure it bel
```go
// Problematic: callers cannot distinguish how items were related.
func(o*Order)AllLineItems()[]LineItem{...}
func(order*Order)AllLineItems()[]LineItem{...}
// Better: the validation logic operates on the full structure.
funcValidateOrder(o*Order)error{...}
funcValidateOrder(order*Order)error{...}
```
Public methods shape how a type is used. Once a lossy accessor exists, callers will depend on it, and the lost information becomes unrecoverable at those call sites.
- Struct receivers: one or two characters (`c`, `f`, `br`).
- Struct receivers: the lowercase name of the role the type plays, the same on every method of the type (`provider`, `module`, `handler`, `store`, `middleware`, `migration`). Never one or two characters:`c`, `h`, `m` say nothing at the call site and collide with local variables.
- When the receiver name would shadow an imported package, alias the import, not the receiver (`pkghandler "github.com/SigNoz/signoz/pkg/http/handler"` with a `handler` receiver).
- **`X` can double as the storage row** when the DB shape would be identical. `Channel` embeds `bun.BaseModel` directly, and there is no `StorableChannel`. This is the preferred shape when it works.
@@ -175,10 +175,10 @@ The rules that make the envelope work:
```go
// Domain invariants: every write path re-runs these.
func (x *X) Validate() error { ... }
func (entity *X) Validate() error { ... }
// Request-shape-only: checks that do not apply once the value is persisted.
- **Type aliases, not wrappers**, when two shapes are identical. `type GettableChannels = []*Channel` is correct because it adds no semantics beyond the underlying type.
- **Serialization tags** follow [handler.md](handler.md): `required:"true"` means the JSON key must be present, `nullable:"true"` is required on any slice or map that may serialize as `null`, and types with a fixed value set must implement `Enum() []any`.
These two folders look similar but mean different things:
- **`fixtures/`** holds *Playwright test fixtures* (created via `test.extend({...})`). By the canonical definition, a fixture is "a consistent, predefined set of data, objects, or environmental conditions used to ensure tests run in a stable state" — i.e. setup/teardown that runs *automatically* around each test or worker. `auth.ts` matches: it extends Playwright's `test` with an`authedPage` that's logged-in before every test runs and torn down after. If the only thing in this folder ever is `auth.ts`, that's fine — fixtures are a deliberately small surface.
- **`helpers/`** holds plain function helpers that you call *explicitly* from a test or hook — they don't extend Playwright's `test`. This covers both behaviour helpers (e.g. `gotoDashboardsList(page)`) and the constants those helpers and the tests both refer to (e.g. `SEARCH_PLACEHOLDER`). Constants live next to the helpers that use them so a single import line in a test covers both.
- **`testdata/`** holds static data files (typically JSON / YAML) consumed by the helpers — for example, `apm-metrics.json`, a real dashboard payload uploaded through the UI by an importer helper.
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/`.
### Drivers
New suites use a driver rather than loose helpers. A driver is a function that closes over the page and returns the feature's actions and locators. `drivers/kit.ts` turns it into a fixture, so a spec imports one module and never passes a `page` around:
A spec imports **one** module: its feature's `test` constant. That constant is
built by composing layers, and each layer adds one named driver to the test
arguments — so a spec body never handles a `Page`:
-`driver(name, build)` adds a test-scoped fixture built on `authedPage`.
-`seeded(name, open, close)` adds a worker-scoped seed. `open` and `close` run on a throwaway admin page.
- A driver reuses helpers by calling them, never by reaching for another fixture.
-`drivers/` is AI-owned: coding agents generate and maintain it with the specs, and `.gitattributes` collapses it in diffs.
### 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>/`.
-**`drivers/kit.ts`** holds the composition primitives. `driver(name, build, dispose?)`
binds a factory to the authenticated page, and `dispose` runs after the test
on the same page so a driver can delete what it seeded; `seeded(name, open, close)` seeds
data once per worker on a throwaway admin page and tears it down after;
`allowFor(floor)` raises the test timeout to a floor and never lowers it.
`test.slow()` is banned in drivers, because it compounds when called in a loop.
- **`drivers/<feature>.ts`** (or `drivers/<feature>/`) exports the feature's
driver, its layers, and the composed `test`. A driver is a plain function
returning an object of closures — no classes, and no `page` parameter on
anything a spec calls.
- **Shared drivers** — `tanstack.ts` (the table), `querysearch.ts` (the
`storage.ts` — are composed into feature drivers by direct call, not by
reaching for another fixture.
- **`drivers/` is AI-owned**: coding agents generate and maintain it with the
specs, and `.gitattributes` collapses it in diffs.
- **`testdata/`** and any `__generated__/` directory hold transcribed data. Both
are marked `linguist-generated`; something in the suite re-derives them at
runtime, so a stale value fails loudly rather than silently.
**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.
- **worker scope** — shared across a worker. Use for read-only data; it pays the
setup cost once per worker instead of once per test. Playwright builds them
lazily, so a spec that never destructures a seed never pays for it.
- **suite scope**: a dataset only one file uses. Seed it in `test.beforeAll`
through `onAdminPage(browser, …)`, since `authedPage` is test-scoped.
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."
- A pure function, a reducer, a config literal or a component's own behaviour is
a **jest** test. Most containers under `frontend/src/container/` already have
one.
- A rendered state that needs no backend (empty, error, loading) is a
**Storybook** story or a jest test with a fixed mock.
- Anything the **API** does — ordering, pagination, filter semantics, validation,
roll-ups — is a pytest integration test, and for infra monitoring and alert
evaluation it very likely already exists. Grep `tests/integration/` first.
- An **E2E** is for a real response reaching a real DOM: URL and history, write
paths that must persist, cross-page navigation, and one seeded proof per
surface.
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.
2.**Test titles use `TC-NN`**: `test('TC-01 alerts page — tabs render', ...)`. Preserves ordering at a glance and maps to external coverage tracking.
3.**UI-first**: drive flows through the UI. Playwright traces capture every BE request/response the UI triggers, so asserting on UI outcomes implicitly validates BE contracts. Reach for direct `page.request.*` only when the test's *purpose* is asserting a response contract (use `page.waitForResponse` on a UI click) or when a specific UI step is structurally flaky (e.g. Ant DatePicker calendar-cell indices) — and even then try UI first.
4.**Self-contained state**: each spec seeds its own data and cleans up at suite teardown. The pytest harness creates a fresh stack with **zero** dashboards / alerts / etc. — never assume pre-existing data. Two patterns work:
- **Per-test seed + cleanup in `try / finally`** — small specs where each test owns its data.
- **Suite-level seed + `afterAll` teardown** — preferred for larger specs. Each `createDashboard(...)` call adds the resulting ID to a module-level `Set<string>`, and one `test.afterAll(...)` deletes everything in the set. See `tests/e2e/tests/dashboards/list.spec.ts` for the full pattern. `test.beforeAll` / `test.afterAll` cannot use `authedPage` directly (it's test-scoped); use `newAdminContext(browser)` from `helpers/auth.ts` instead — it performs one fresh login per suite hook.
5.**Seed via API when the UI flow is multi-step or brittle.** The frontend stores its JWT in `localStorage` under `AUTH_TOKEN`; `page.request.*` inherits the auth fixture's storage state. A typical pattern:
```ts
const token = await page.evaluate(
() => (globalThis as any).localStorage.getItem('AUTH_TOKEN') || '',
This is faster and more reliable than a multi-step UI seed. Reach for the UI flow only when the test's *purpose* is asserting that flow.
6. **Reusable static data lives in `tests/e2e/testdata/`.** For example, `apm-metrics.json` is a real dashboard payload that `importApmMetricsDashboardViaUI` (in `helpers/dashboards.ts`) uploads through the actual Import JSON UI flow to seed a richly-tagged dashboard for search/list tests.
3.**UI-first**: drive flows through the UI. Playwright traces capture every BE request/response the UI triggers, so asserting on UI outcomes implicitly validates BE contracts. To assert a write, attach `page.waitForResponse` before the click, then reload and re-read from the server.
4.**Self-contained state**: each spec seeds its own data, scopes its assertions to the names it seeded, and cleans up. The pytest harness creates a fresh stack with **zero** dashboards / alerts / etc. Names carry the worker index, so parallel workers never collide. Pick the scope from the list above.
5.**Seed through the kit when the UI flow is not under test.**`apiDriver(page)` (`drivers/api.ts`) creates control-plane objects with the session's token, and the seeder takes telemetry. The JWT lives in `localStorage`, so a spec never improvises with `page.request.*`. Feature drivers wrap both (`dashboards.seed`, `alerts.seed.threshold`).
6.**Reusable static data lives in `tests/e2e/testdata/`.** For example, `traces/large-trace.json` is POSTed to the seeder by `drivers/traces.ts`. Infra monitoring reads the integration suite's fixtures through `seedDataset` (`drivers/infra/seed.ts`) instead of keeping a copy, and asserts on what it returned.
7.**Locate by testid, in the driver.** A missing testid is added to the product in the same change. Role, label and CSS lookups are a stopgap, converted as that code is touched; antd internals (`.ant-select-dropdown`) are the exception, since the markup is not ours.
## How to write an E2E test?
Create a new file `tests/e2e/tests/alerts/smoke.spec.ts`:
The `authedPage` fixture (from `tests/e2e/fixtures/auth.ts`) gives you a `Page` whose browser context is already authenticated as the admin user. First use per worker triggers one login; the resulting `storageState` is held in memory and reused for later requests.
`alertList` is a worker-scoped seed: the rules exist before the test starts, are
shared by every test in the worker that asks for them, and are deleted when the
worker ends.
The `authedPage` fixture (from `tests/e2e/drivers/kit.ts`, re-exported by every feature's `test`) gives you a `Page` whose browser context is already authenticated as the admin user. First use per worker triggers one login; the resulting `storageState` is held in memory and reused for later requests. Take it only when the driver genuinely does not cover what you need — navigation, `goBack`, `waitForResponse`, a second tab.
To run just this test (assuming the stack is up via `test_setup`):
@@ -212,52 +204,56 @@ cd tests/e2e
npx playwright test tests/alerts/smoke.spec.ts --project=chromium
```
Here's a more comprehensive example that exercises a CRUD flow via the UI:
Here's a more comprehensive example, driven entirely through the feature's
`ownedRules` is a test-scoped seed: every rule it creates is deleted when the
test finishes, so the spec owns its data and nothing leaks into the next one.
1. `getByTestId('...')` — preferred when the source exposes one. Stable, app-author-provided handle that survives copy-edits.
2. `getByRole('button', { name: 'Submit' })`
3. `getByLabel('Email')`
4. `getByPlaceholder('...')`
5. `getByText('...')`
6. `locator('.ant-select')` — last resort (Ant Design dropdowns often have no semantic alternative)
### Commits
Commit driver changes apart from specs, product testids and tests moved to
another layer, so each commit can be reviewed on its own.
[`tests/e2e/AGENTS.md`](../../../tests/e2e/AGENTS.md#commits) has the order.
`pnpm typecheck`, `pnpm lint` and `pnpm fmt:check` gate the suite.
## Agents
Three Claude agents in `.claude/agents/` accelerate writing and maintaining E2E specs:
- **`playwright-test-planner`** — explores a feature in a real browser plus the local frontend source and writes a test plan as a scratch markdown file (under `tests/e2e/specs/`, which is gitignored — plans are working artifacts for the generator, not committed docs).
- **`playwright-test-generator`** — converts a test plan into Playwright spec files under `tests/e2e/tests/<feature>/`. Drives each scenario through MCP browser tools and emits TC-NN-titled tests using the `authedPage` fixture and the API-seed pattern.
- **`playwright-test-generator`** — converts a test plan into Playwright spec files under `tests/e2e/tests/<feature>/`. Drives each scenario through MCP browser tools and emits TC-NN-titled tests on the feature's driver, adding the driver methods and seeds the plan needs.
- **`playwright-test-healer`** — runs failing specs, debugs them with snapshots / console / network introspection, and edits the spec to fix selector drift, timing, or state-leak issues.
The agents rely on the Playwright-test MCP server (`mcp__playwright-test__*` tools). Configure it in your Claude MCP settings; the permission allowlist lives in [.claude/settings.local.json](../../../.claude/settings.local.json).
@@ -269,15 +265,15 @@ The agents rely on the Playwright-test MCP server (`mcp__playwright-test__*` too
With the stack already up, from `tests/e2e/`:
```bash
yarn test # headless, all projects
pnpm test# headless, all projects
```
### Running Specific Projects
```bash
yarn test:chromium # chromium only
yarn test:firefox
yarn test:webkit
pnpm test:chromium # chromium only
pnpm test:firefox
pnpm test:webkit
```
### Running Specific Tests
@@ -295,17 +291,17 @@ npx playwright test tests/alerts/history/ --project=chromium
npx playwright test tests/alerts/page.spec.ts --project=chromium
pnpm report # open the last HTML report (artifacts/html)
```
### Staging fallback
@@ -315,7 +311,7 @@ Point `SIGNOZ_E2E_BASE_URL` at a remote env via `.env` — no local backend brin
```bash
cd tests/e2e
cp .env.example .env # fill SIGNOZ_E2E_USERNAME / PASSWORD
yarn test:staging
pnpm test:staging
```
## How to configure different options for E2E tests?
@@ -363,12 +359,14 @@ The same pytest flags integration tests expose work here, since E2E reuses the s
- **Always use the `--reuse` flag** when setting up the E2E stack. `--with-web` adds a ~4 min frontend build on a cold start; later builds are incremental.
- **Changed backend or frontend code? Re-run the setup command** — it passes `--rebuild`, swapping the SigNoz container for one built from your current sources while the rest of the stack stays up.
- **Don't teardown before setup.** `--reuse` correctly handles partially-set-up state, so chaining teardown → setup wastes time.
- **Prefer UI-driven flows.** Playwright captures BE requests in the trace; a parallel `fetch` probe is almost always redundant. Drop to `page.request.*` only when the UI can't reach what you need.
- **Prefer UI-driven flows.** Playwright captures BE requests in the trace; a parallel `fetch` probe is almost always redundant. Seed through the kit, never with `page.request.*`.
- **Use `page.waitForResponse` on UI clicks** to assert BE contracts — it still exercises the UI trigger path.
- **Title every test `TC-NN <short description>`** — keeps the suite navigable and reportable.
- **Split by resource, not by regression suite.** One spec per feature resource; cross-resource junction concerns (cascade-delete, linked-edit) get their own file.
- **Use short descriptive resource names** (`alerts-list-rule`, `labels-rule`, `downtime-once`) — no timestamp disambiguation. Each test owns its resources and cleans up in `try/finally`.
- **Name resources by what they are, plus the worker index** (`e2e-alert-list-w${workerIndex}-${Date.now()}`). Six workers share one stack, so assert only on names you seeded.
- **Never commit `test.only`** — a pre-commit check or CI runs with `forbidOnly: true`.
- **Prefer explicit waits over `page.waitForTimeout(ms)`.** `await expect(locator).toBeVisible()` is always better than `waitForTimeout(5000)`.
- **Unique test names won't save you from shared-tenant state.** When two tests hit the same list page, either serialize (`describe.configure({ mode: 'serial' })`) or isolate cleanup religiously.
- **Green locally, red on CI?** Run `STRESS=1 CI=1 pnpm test <path>` (4x CPU throttle, 2 workers, 2 retries) before filing a product bug.
- **Commit drivers apart from specs.** One concern per commit; see [Commits](#commits).
- **Artifacts go to `tests/e2e/artifacts/`** — HTML report at `artifacts/html`, traces at `artifacts/results/<test>/`. All gitignored; archive the dir in CI.
- **Do not use Redux/Context** to store or mirror data that comes from React Query (e.g. do not dispatch API results into Redux).
- Prefer generated React Query hooks from `frontend/src/api/generated` when available.
- Keep server state in React Query; expose it via hooks that return the query result (and optionally memoized derived values). Do not duplicate it in Redux or Context.
```tsx
// ✅ GOOD: single source of truth from React Query
// ❌ BAD: no selector — re-renders on any store change
const state = useDashboardStore()
```
- **Never mutate state directly.** Update only via `set` or `setState` (or `getState()` + `set` for reads). No `state.foo = x` or `state.bears += 1` inside actions.
- **State properties before actions.** In the store object, list all state fields first, then action functions.
- **Split into slices when state is large.** If a store has many top-level properties (e.g. more than 5–10), split into slice factories and combine with one `create()`.
description: Prefer SigNoz UI and icons across frontend code
globs: **/*.{ts,tsx,js,jsx}
alwaysApply: true
---
# UI Components and Icons Source of Truth
For all frontend implementation work in this repository:
- Always use UI primitives/components from `@signozhq/ui`.
- Always use icons from `@signozhq/icons`.
- Do not introduce new usage of icon libraries directly (for example `lucide-react`) in app code.
- Do not mix multiple component systems for the same UI surface when an equivalent exists in `@signozhq/ui`.
## Migration guidance
- If touching a file that already uses non-`@signozhq/icons` icons, prefer migrating that file to `@signozhq/icons` as part of the same change when practical.
- If a required component or icon is missing from SigNoz packages, call this out explicitly in the PR/summary before introducing alternatives.
@@ -25,15 +25,24 @@ You are operating within a constrained context window and strict system prompts.
- Never create barrel files.
- When writing new css, prefer CSS Modules
- Use ./docs/css-modules-guide.md as reference on how to write good CSS Modules.
- UI primitives come from `@signozhq/ui` (subpath imports) and icons from `@signozhq/icons`. No new `antd` component or direct icon library (`lucide-react`) where `@signozhq/ui` has an equivalent; `signoz/no-antd-components` enforces part of this list.
- Exception: a control added beside `antd` siblings of the same kind (a tab in an `antd` tab strip) matches them until that surface migrates. Say so in the PR.
- When `@signozhq/ui` or `@signozhq/icons` lacks what you need, say so in the PR before reaching for another library.
- Before styling a `@signozhq/ui` component, check its props in `node_modules/@signozhq/ui/dist/<component>/*.d.ts` for `className`/`style` support; some omit them. Avoid overriding the component's CSS vars unless explicitly needed.
- When writing code that could need authorization checks, read ./src/lib/authz/README.md
- A flow that gains a request also gates on that request's permission: a rename that now reads the view first needs `read` as well as `update`.
- Identity keys (legend groups, color groups, CSV headers, React keys) come from structured values, never from joined display text: `['us, east', 'prod']` and `['us', 'east, prod']` both join to `us, east, prod`.
- A payload saved under another kind or schema copies only the fields that schema accepts, and drops bindings to columns the user removed. The server rejects unknown or stale fields, so the preview works and the save fails.
3. FORCED VERIFICATION: Your internal tools mark file writes as successful even if the code does not compile. You are FORBIDDEN from reporting a task as complete until you have:
3. FORCED VERIFICATION: Your internal tools mark file writes as successful even if the code does not compile. Run these checks once all changes of a task are done, never after each edit. You are FORBIDDEN from reporting a task as complete until you have:
- Run `pnpm tsgo --noEmit`
- Run `pnpm lint:js --quiet` to find critical errors
- Run `pnpm oxlint <file1> <file2>` and fix all warnings
- Run `pnpm build`
- Find if the file has tests for it, or if there's `__test__` folder or the parent folder has tests, and run.
- Find if the file has tests for it, or if there's `__test__` folder or the parent folder has tests, and run. Run them before coding the change or after all changes, never in between.
- For e2e, run only the specs you added or changed: the stack is heavy. Leave the full jest and e2e suites to CI unless the user asks.
- For every test you added or changed, revert the change it covers and confirm the test fails
- On a stacked branch (`gh stack`), run `pnpm tsgo --noEmit` on every branch above it before pushing: a renamed prop or moved export breaks them
- Fixed ALL resulting errors
4. BEHAVIOR CHANGE DETECTION: When modifying existing behavior:
@@ -41,6 +50,14 @@ You are operating within a constrained context window and strict system prompts.
- Update test assertions to match new behavior
- If no tests exist, add them BEFORE changing behavior
## Repo Rules
Claude Code loads `../.claude/rules/` on its own. Other agents read the matching file before editing:
- Every file: [comments](../.claude/rules/comments.md), [pull requests](../.claude/rules/pull-requests.md)
-`src/**/*.ts(x)` that holds state, a query, URL params or a form: [frontend state](../.claude/rules/frontend-state.md)
-`*.tsx` that changes what a page renders: [storybook](../.claude/rules/storybook.md)
## Context Management
1. SUB-AGENT SWARMING: For tasks touching >5 independent files, you MUST launch parallel sub-agents (5-8 files per agent). Each agent gets its own context window. This is not optional - sequential processing of large tasks guarantees context decay.
'Disallow direct react-router / lib/history imports; import from src/lib/router instead',
category:'React Router migration',
},
schema:[],
messages:{
v5Router:
'Do not import react-router-dom directly. Use the src/lib/router facade (useAppNavigate, useAppLocation, useAppParams, AppLink, Redirect, matchRoute) so a router upgrade stays contained.',
v6Compat:
'Do not import react-router-dom-v5-compat directly. Use the src/lib/router facade instead; the compat package is an implementation detail of the migration and disappears with it.',
historySingleton:
'Do not import the lib/history singleton. Use the helpers in src/lib/router/navigation.ts, or useAppNavigate() to follow the nearest router. history loses basename handling once the router owns it.',
mock:
'Do not mock the router directly. Render at a route with tests/test-utils, or spy on the facade with the tests/router-mocks helpers.',
"tooltip_opsgenie_api_key":"Learn how to obtain the API key from your OpsGenie account [here](https://support.atlassian.com/opsgenie/docs/integrate-opsgenie-with-prometheus/).",
"tooltip_email_to":"Enter email addresses separated by commas.",
"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",
"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.",
"jira_selfhosted_tip":"Works with Jira Data Center 8.14 and newer. Create the personal access token from a dedicated service user so issues aren't reported under a personal name.",
"jira_selfhosted_tip_link":"Learn how",
"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",
"tooltip_jira_site":"Your Jira Cloud base URL, e.g. https://your-domain.atlassian.net, or a proxy in front of it.",
"tooltip_jira_site_selfhosted":"Your self-hosted Jira base URL, e.g. https://jira.your-company.com, or a proxy in front of it.",
"jira_required_fields":"Site URL, project and issue type are required",
"jira_cloud_credentials_required":"Email and API token are required for Jira Cloud",
"jira_pat_required":"A personal access token is required for self-hosted Jira",
"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_pat":"Personal access token",
"help_jira_pat":"Create one in Jira under your profile, Personal Access Tokens. Sent as a Bearer header.",
"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.",
@@ -36,6 +44,7 @@
"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.",
"help_jira_description_selfhosted":"Template for the issue description. Rendered as Jira wiki markup 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",
@@ -82,7 +91,6 @@
"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 }}.",
"placeholder_incidentio_metadata_key":"Key",
@@ -92,8 +100,8 @@
"field_slack_recipient":"Recipient",
"field_slack_title":"Title",
"field_slack_description":"Description",
"field_webhook_username":"User Name (optional)",
"field_webhook_password":"Password (optional)",
"field_webhook_username":"Username",
"field_webhook_password":"Password",
"field_pager_routing_key":"Routing Key",
"field_pager_description":"Description",
"field_pager_severity":"Severity",
@@ -106,19 +114,16 @@
"placeholder_slack_description":"Description",
"placeholder_pager_description":"Description",
"help_pager_client":"Shows up as event source in Pagerduty",
"help_pager_client_url":"Shows up as event source link in Pagerduty",
"help_pager_client_url":"Shows up as event source link in Pagerduty. Leave empty to link to this SigNoz instance",
"help_pager_class":"The class/type of the event",
"help_pager_details":"Specify a key-value format (must be a valid json)",
"help_pager_group":"A cluster or grouping of sources",
"help_pager_component":"The part or component of the affected system that is broke",
"help_pager_severity":"Severity of the incident, must be one of: must be one of the following: 'critical', 'warning', 'error' or 'info'",
"help_webhook_username":"Leave empty for bearer auth or when authentication is not necessary.",
"help_webhook_password":"Specify a password or bearer token",
"help_pager_description":"Shows up as description in pagerduty",
"channel_creation_done":"Successfully created the channel",
"tooltip_opsgenie_api_key":"Learn how to obtain the API key from your OpsGenie account [here](https://support.atlassian.com/opsgenie/docs/integrate-opsgenie-with-prometheus/).",
"tooltip_email_to":"Enter email addresses separated by commas.",
"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",
"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.",
"jira_selfhosted_tip":"Works with Jira Data Center 8.14 and newer. Create the personal access token from a dedicated service user so issues aren't reported under a personal name.",
"jira_selfhosted_tip_link":"Learn how",
"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",
"tooltip_jira_site":"Your Jira Cloud base URL, e.g. https://your-domain.atlassian.net, or a proxy in front of it.",
"tooltip_jira_site_selfhosted":"Your self-hosted Jira base URL, e.g. https://jira.your-company.com, or a proxy in front of it.",
"jira_required_fields":"Site URL, project and issue type are required",
"jira_cloud_credentials_required":"Email and API token are required for Jira Cloud",
"jira_pat_required":"A personal access token is required for self-hosted Jira",
"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_pat":"Personal access token",
"help_jira_pat":"Create one in Jira under your profile, Personal Access Tokens. Sent as a Bearer header.",
"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.",
@@ -44,6 +52,7 @@
"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.",
"help_jira_description_selfhosted":"Template for the issue description. Rendered as Jira wiki markup 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",
@@ -90,7 +99,8 @@
"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>)",
"incidentio_token_bearer_prefix":"Paste the token only, without the Bearer prefix",
"incidentio_token_whitespace":"Remove the spaces around the token",
"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 }}.",
"placeholder_incidentio_metadata_key":"Key",
@@ -116,6 +126,7 @@
"field_slack_field_short":"Short",
"add_slack_field":"Add field",
"remove_slack_field":"Remove field",
"slack_field_value_required":"Enter a value for this field",
"field_slack_actions":"Actions",
"help_slack_actions":"Buttons rendered under the attachment. A button with a URL links out.",
"label_slack_action_text":"Text",
@@ -133,6 +144,7 @@
"placeholder_slack_action_style":"Style: default, primary or danger",
* This endpoint lists all alert rules with their current evaluation state. Deprecated: use ListRulesV3, which supports filtering, sorting and pagination.
* @description The Atlassian API token paired with email. Required when deployment is cloud. A secret; returned by create, update and fetch by ID, never by list.
*/
apiToken?: string;
/**
* @type string
* @description The Atlassian account email used for basic auth. Required when deployment is cloud.
*/
email?: string;
/**
* @type string
* @format password
* @description The Jira personal access token, sent as a Bearer header. Required when deployment is selfhosted. A secret; returned by create, update and fetch by ID, never by list.
*/
pat?: string;
}
exportenumAlertmanagertypesJiraDeploymentDTO{
cloud='cloud',
selfhosted='selfhosted',
}
/**
* Extra issue fields keyed by Jira field ID. Stored, but not currently included in the issue Jira receives.
*/
@@ -844,26 +868,23 @@ export type AlertmanagertypesChannelJiraConfigDTOCustomFields = Alertmanagertype
* @description The Jira edition, cloud or selfhosted; defaults to cloud.
*/
deployment?: AlertmanagertypesJiraDeploymentDTO;
/**
* @type string
* @description Alertmanager template for the issue description.
*/
description?: string;
/**
* @description HTTP client settings; basic auth carries the account email and API token.
* @description HTTP client settings; basic auth carries the account email and API token on cloud, authorization carries the Bearer personal access token on self-hosted.
* @description The opaque nextCursor value from a previous raw result, used to fetch the next page.
* @description The opaque nextCursor value from a previous raw result, used to fetch the next page. Honored only by raw requests whose order starts with timestamp, for logs followed by id in the same direction; every other request ignores it.
* @description The maximum number of rows for raw requests, or of series or rows for grouped aggregations, keeping the top entries by order. When 0, aggregations are unlimited while raw requests ordered by timestamp return a page of 10000 rows; follow nextCursor for the rest.
* @description The maximum number of rows for raw and trace requests, or of series or rows for grouped scalar and time_series requests, keeping the top entries by order. When 0, scalar and time_series requests are unlimited, raw requests ordered by timestamp return a page of 10000 rows to continue with nextCursor, and other raw and trace requests return 100 rows.
* @description The number of rows to skip before returning results. Prefer cursor for raw requests.
* @description The number of rows for raw and trace requests, or of ordered series or rows for scalar and time_series requests, to skip before the page starts. Requires limit. On raw requests the skip counts from where a cursor resumes, and the skipped rows are still read, so prefer cursor for paging.
* @description The opaque nextCursor value from a previous raw result, used to fetch the next page.
* @description The opaque nextCursor value from a previous raw result, used to fetch the next page. Honored only by raw requests whose order starts with timestamp, for logs followed by id in the same direction; every other request ignores it.
* @description The maximum number of rows for raw requests, or of series or rows for grouped aggregations, keeping the top entries by order. When 0, aggregations are unlimited while raw requests ordered by timestamp return a page of 10000 rows; follow nextCursor for the rest.
* @description The maximum number of rows for raw and trace requests, or of series or rows for grouped scalar and time_series requests, keeping the top entries by order. When 0, scalar and time_series requests are unlimited, raw requests ordered by timestamp return a page of 10000 rows to continue with nextCursor, and other raw and trace requests return 100 rows.
* @description The number of rows to skip before returning results. Prefer cursor for raw requests.
* @description The number of rows for raw and trace requests, or of ordered series or rows for scalar and time_series requests, to skip before the page starts. Requires limit. On raw requests the skip counts from where a cursor resumes, and the skipped rows are still read, so prefer cursor for paging.
* @description The opaque nextCursor value from a previous raw result, used to fetch the next page.
* @description The opaque nextCursor value from a previous raw result, used to fetch the next page. Honored only by raw requests whose order starts with timestamp, for logs followed by id in the same direction; every other request ignores it.
* @description The maximum number of rows for raw requests, or of series or rows for grouped aggregations, keeping the top entries by order. When 0, aggregations are unlimited while raw requests ordered by timestamp return a page of 10000 rows; follow nextCursor for the rest.
* @description The maximum number of rows for raw and trace requests, or of series or rows for grouped scalar and time_series requests, keeping the top entries by order. When 0, scalar and time_series requests are unlimited, raw requests ordered by timestamp return a page of 10000 rows to continue with nextCursor, and other raw and trace requests return 100 rows.
* @description The number of rows to skip before returning results. Prefer cursor for raw requests.
* @description The number of rows for raw and trace requests, or of ordered series or rows for scalar and time_series requests, to skip before the page starts. Requires limit. On raw requests the skip counts from where a cursor resumes, and the skipped rows are still read, so prefer cursor for paging.
* @description The opaque nextCursor value from a previous raw result, used to fetch the next page.
* @description The opaque nextCursor value from a previous raw result, used to fetch the next page. Honored only by raw requests whose order starts with timestamp, for logs followed by id in the same direction; every other request ignores it.
* @description The maximum number of rows for raw requests, or of series or rows for grouped aggregations, keeping the top entries by order. When 0, aggregations are unlimited while raw requests ordered by timestamp return a page of 10000 rows; follow nextCursor for the rest.
* @description The maximum number of rows for raw and trace requests, or of series or rows for grouped scalar and time_series requests, keeping the top entries by order. When 0, scalar and time_series requests are unlimited, raw requests ordered by timestamp return a page of 10000 rows to continue with nextCursor, and other raw and trace requests return 100 rows.
* @description The number of rows to skip before returning results. Prefer cursor for raw requests.
* @description The number of rows for raw and trace requests, or of ordered series or rows for scalar and time_series requests, to skip before the page starts. Requires limit. On raw requests the skip counts from where a cursor resumes, and the skipped rows are still read, so prefer cursor for paging.
* @description Axis title. Empty draws none; the tooltip still names the column.
*/
label?: string;
/**
* @description auto (default) uses log, or symlog when values include zero or negatives, when that spreads the dots clearly more than linear does; otherwise linear. log cannot place a value at or below zero and leaves that dot out; symlog is log-like but places it.
*/
scale?: DashboardtypesAxisScaleDTO;
/**
* @type number,null
* @description The highest value the axis always shows; data above it still extends the axis, as does a threshold on it. Null fits the data.
*/
softMax?: number|null;
/**
* @type number,null
* @description The lowest value the axis always shows; data below it still extends the axis, as does a threshold on it. Null fits the data. A log axis ignores a bound at or below zero.
*/
softMin?: number|null;
}
exportinterfaceDashboardtypesScatterPlotAxesDTO{
/**
* @description The axis dimensions.x is plotted along.
*/
x?: DashboardtypesScatterPlotAxisDTO;
/**
* @description The axis dimensions.y is plotted along.
* @description Group-by label names (e.g. k8s.namespace.name) whose combined values color the dots and drive the legend. Empty colors by every group-by label.
*/
colorBy?: string[]|null;
/**
* @type string
* @description Value column key that scales each dot's area between chartAppearance.points.minSize and maxSize. Empty draws every dot at chartAppearance.points.size, as does a dot with no value.
*/
sizeBy?: string;
/**
* @type string
* @description Value column key (queryName, or queryName.expression for a multi-aggregation query) plotted on the x axis. Empty, or a key the result lacks, uses the first value column.
*/
x?: string;
/**
* @type string
* @description Value column key plotted on the y axis. Empty, or a key the result lacks, uses the first value column x does not use.
* @description The axis the line marks: y (default) draws it horizontally, x vertically. Unit converts against that axis's column unit, and the axis extends to keep the line in view.
*/
axis?: DashboardtypesThresholdAxisDTO;
/**
* @type string
* @description The CSS color of the line, for example #F1575F, which matches the SigNoz red preset; CSS named colors such as red also work.
*/
color: string;
/**
* @type string
* @description The caption shown on the line.
*/
label?: string;
/**
* @type string
* @description The unit id of value, such as ms or percent. Empty means the panel's unit, or the column's unit for a table threshold. Prefer the panel's unit; a different unit works only when it converts to it, such as s against a panel in ms.
*/
unit?: string;
/**
* @type number
* @format double
* @description The value the line is drawn at, in unit.
* @description Per-column units and decimal precision. The x and y columns' units label their axes; every bound column's unit shows in the tooltip.
*/
formatting?: DashboardtypesTableFormattingDTO;
/**
* @description Legend placement and series colors. A series is one color group, labeled by its colorBy values joined with a comma, or All when nothing is grouped.
*/
legend?: DashboardtypesLegendDTO;
/**
* @type array,null
* @description Reference lines drawn at fixed values across the x or y axis.
* @description Accepted but currently ignored; the computed values are returned unfiltered.
* @description A filter on the formula values, referencing the formula by its name or as __result, for example F1 > 0. A time series keeps only the points that match and drops a series with none left; a scalar result keeps only the rows that match.
* @description The unique name of the formula, such as F1, used by selectedQueryName and by panels. Referencing a formula from another formula is not reliable.
*/
name?: string;
/**
* @type integer
* @description The number of ordered series or rows to skip before the page starts. Requires limit.
*/
offset?: number;
/**
* @type array,null
* @description The sort order of the formula result.
* @description The number of raw results to skip before returning. Aggregation requests ignore it.
* @description The number of raw or trace rows, or of ordered series or rows for time_series and scalar requests, to skip before the page starts. Requires limit.
* @description The ascending upper bounds of the value bands, set only for heatmap results; each point then carries one count per bound plus a trailing overflow count in values.
* @description The ascending upper bounds of the value buckets, set for heatmap and distribution results; each point then carries one count per bound plus a trailing overflow count in values. The first bucket starts at -Inf when its bound is at or under zero, else at zero.
* @description The zero-based position of the aggregation in the query.
*/
index: number;
/**
* @description Display metadata for the aggregation, including the bucket upper bounds the counts are positional against.
*/
meta?: Querybuildertypesv5AggregationMetaDTO;
/**
* @description Percentiles over the counts of every distribution added together, estimated the way histogram_quantile does by interpolating linearly within the bucket each rank falls in. Omitted when nothing was counted or meta.buckets is empty.
* @description The name of the query that produced this result.
*/
queryName: string;
}
/**
* The step interval in seconds that was used for each query, keyed by query name.
*/
@@ -11767,21 +12087,28 @@ export type Querybuildertypesv5QueryDataDTO =
|(Querybuildertypesv5TimeSeriesDataDTO&{
/**
* @type array,null
* @description One result per enabled query, typed by the request type: TimeSeriesData for time_series, ScalarData for scalar and RawData for raw requests.
* @description One result per enabled query, typed by the request type: TimeSeriesData for time_series and heatmap, ScalarData for scalar, RawData for raw and DistributionData for distribution requests.
*/
results?: unknown[]|null;
})
|(Querybuildertypesv5ScalarDataDTO&{
/**
* @type array,null
* @description One result per enabled query, typed by the request type: TimeSeriesData for time_series, ScalarData for scalar and RawData for raw requests.
* @description One result per enabled query, typed by the request type: TimeSeriesData for time_series and heatmap, ScalarData for scalar, RawData for raw and DistributionData for distribution requests.
*/
results?: unknown[]|null;
})
|(Querybuildertypesv5RawDataDTO&{
/**
* @type array,null
* @description One result per enabled query, typed by the request type: TimeSeriesData for time_series, ScalarData for scalar and RawData for raw requests.
* @description One result per enabled query, typed by the request type: TimeSeriesData for time_series and heatmap, ScalarData for scalar, RawData for raw and DistributionData for distribution requests.
*/
results?: unknown[]|null;
})
|(Querybuildertypesv5DistributionDataDTO&{
/**
* @type array,null
* @description One result per enabled query, typed by the request type: TimeSeriesData for time_series and heatmap, ScalarData for scalar, RawData for raw and DistributionData for distribution requests.
* Response from the v5 query range endpoint. The data.results array contains typed results depending on the requestType: TimeSeriesData for time_series, ScalarData for scalar, or RawData for raw requests.
* Response from the v5 query range endpoint. The data.results array contains typed results depending on the requestType: TimeSeriesData for time_series and heatmap, ScalarData for scalar, RawData for raw, or DistributionData for distribution requests.
* @description Whether to fire a nodata alert when the query returns no data. Defaults to false.
*/
alertOnAbsent?: boolean;
/**
* @type string
* @description anomaly_rule only: the anomaly detection algorithm. The supported value is standard.
*/
algorithm?: string;
/**
* @description The queries whose result is compared against the thresholds.
*/
compositeQuery: RuletypesAlertCompositeQueryDTO;
/**
* @description schemaVersion v1 only: which points in the window must satisfy op. Accepted as at_least_once, all_the_times, on_average, in_total or last, their aliases avg and sum, or the legacy codes as strings, '1' to '5'. Required for v1; v2alpha1 sets matchType on each threshold.
*/
matchType?: RuletypesMatchTypeDTO;
/**
* @description schemaVersion v1 only: how the value is compared with target. Accepted as above, below, equal, not_equal, above_or_equal, below_or_equal or outside_bounds, their symbols, or the legacy codes as strings, '1' to '7'. Required for v1; v2alpha1 sets op on each threshold.
*/
op?: RuletypesCompareOperatorDTO;
/**
* @type boolean
* @description Whether a series is skipped until it has at least requiredNumPoints points in the window. Defaults to false.
* @description The name of the query whose result is evaluated, such as A or F1. Must match a query in compositeQuery. When omitted, F1 is used if present, otherwise the enabled query with the highest name.
* @description The name of the query whose result is evaluated, such as A or F1. Must match a query in compositeQuery.
*/
selectedQueryName?: string;
selectedQueryName: string;
/**
* @type number,null
* @format double
* @description schemaVersion v1 only: the value compared against, in targetUnit. Required for v1; v2alpha1 sets target on each threshold.
* @description The thresholds to evaluate, as a kind and spec.
*/
target?: number|null;
/**
* @type string
* @description schemaVersion v1 only: the unit of target, such as percent or ms. Converted to compositeQuery.unit before comparison when the two differ.
*/
targetUnit?: string;
/**
* @description The thresholds to evaluate, as a kind and spec. Required when schemaVersion is v2alpha1; derived from op, target and matchType for v1.
* @description The lookback window as a Go duration such as 5m. Only read when schemaVersion is v1, defaulting to 5m; v2alpha1 rules set it in evaluation.spec instead.
* @description The evaluation window, as a kind and spec.
*/
evalWindow?: string;
/**
* @description The evaluation window, as a kind and spec. Required when schemaVersion is v2alpha1; derived from evalWindow and frequency for v1.
*/
evaluation?: RuletypesEvaluationEnvelopeDTO;
/**
* @type string
* @description How often the rule is evaluated, as a Go duration such as 1m. Only read when schemaVersion is v1, defaulting to 1m; v2alpha1 rules set it in evaluation.spec instead.
*/
frequency?: string;
evaluation: RuletypesEvaluationEnvelopeDTO;
/**
* @type object
* @description Labels attached to every alert the rule fires. Keys match [a-zA-Z_.][a-zA-Z0-9_.]*; values may use Go templates such as {{$labels.service_name}}. severity is the conventional routing label.
*/
labels?: RuletypesPostableRuleDTOLabels;
/**
* @description Grouping, re-notification and routing behaviour. Required when schemaVersion is v2alpha1; derived from the v1 fields otherwise.
* @description Grouping, re-notification and routing behaviour.
* @description The displayName of each notification channel to notify, for schemaVersion v1 rules. Ignored for v2alpha1, where each threshold carries its own channels.
*/
preferredChannels?: string[];
/**
* @description The evaluation strategy: threshold_rule compares against thresholds, promql_rule evaluates a PromQL expression, anomaly_rule scores deviation from seasonal history. Defaults to threshold_rule for builder queries and is forced to promql_rule for promql queries.
* @description The evaluation strategy: threshold_rule compares against thresholds, promql_rule evaluates a PromQL expression, anomaly_rule scores deviation from seasonal history. Defaults to threshold_rule for builder and clickhouse_sql queries and is forced to promql_rule for promql queries.
*/
ruleType: RuletypesRuleTypeDTO;
/**
* @enum v2alpha1
* @type string
* @description The rule payload schema: v1 (the default when omitted) uses the flat condition fields, v2alpha1 uses condition.thresholds, evaluation and notificationSettings.
* @description The rule payload schema.
*/
schemaVersion?: string;
/**
* @type string
* @description The URL of the page the rule was created from. Informational; it is not used for evaluation.
*/
source?: string;
/**
* @type string
* @description The query payload version. Only v5 is accepted and it is filled in when omitted.
* @description The lookback window as a Go duration such as 5m. Only read when schemaVersion is v1, defaulting to 5m; v2alpha1 rules set it in evaluation.spec instead.
* @description The evaluation window, as a kind and spec.
*/
evalWindow?: string;
/**
* @description The evaluation window, as a kind and spec. Required when schemaVersion is v2alpha1; derived from evalWindow and frequency for v1.
*/
evaluation?: RuletypesEvaluationEnvelopeDTO;
/**
* @type string
* @description How often the rule is evaluated, as a Go duration such as 1m. Only read when schemaVersion is v1, defaulting to 1m; v2alpha1 rules set it in evaluation.spec instead.
* @description The displayName of each notification channel to notify, for schemaVersion v1 rules. Ignored for v2alpha1, where each threshold carries its own channels.
*/
preferredChannels?: string[];
/**
* @description The evaluation strategy: threshold_rule compares against thresholds, promql_rule evaluates a PromQL expression, anomaly_rule scores deviation from seasonal history. Defaults to threshold_rule for builder queries and is forced to promql_rule for promql queries.
* @description The evaluation strategy: threshold_rule compares against thresholds, promql_rule evaluates a PromQL expression, anomaly_rule scores deviation from seasonal history. Defaults to threshold_rule for builder and clickhouse_sql queries and is forced to promql_rule for promql queries.
*/
ruleType: RuletypesRuleTypeDTO;
/**
* @enum v2alpha1
* @type string
* @description The rule payload schema: v1 (the default when omitted) uses the flat condition fields, v2alpha1 uses condition.thresholds, evaluation and notificationSettings.
* @description The rule payload schema.
*/
schemaVersion?: string;
/**
* @type string
* @description The URL of the page the rule was created from. Informational; it is not used for evaluation.
*/
source?: string;
schemaVersion: RuletypesRuleDTOSchemaVersion;
/**
* @description The current evaluation state: inactive, pending, recovering, firing, nodata or disabled.
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.