Husky wired hook installation to pnpm install, which go-only contributors
never run. The installer is plain shell so both the Makefile and the frontend
postinstall can call it, and each check is gated on staged paths so neither
side needs the other's toolchain.
#### Description
- The processor form pre-fills `parse_from` with `body` for the grok,
regex and json parsers (`initialValue: 'body'` in
`AddNewProcessor/config.ts`).
- With `use_json_body` the collector prepends a `normalize` pipeline, so
by the time user operators run the body is a map. A parser pointed at
`body` gets a map it cannot parse and silently extracts nothing — the
pipeline is broken by default, without the user ever touching the field.
- Resolve the default to `body.message` when the flag is on. Keying off
`initialValue === 'body'` rather than a hardcoded processor list keeps
`time_parser` (`attributes.timestamp`) and `severity_parser`
(`attributes.logLevel`) untouched, and covers any future processor that
defaults to the body.
#### Additional Information
- Saved processors are unaffected — edit mode calls
`form.setFieldsValue(savedData)`, which overrides `initialValue`. This
only changes what a newly added processor starts with, and only while
the flag is on.
- The helper returns new objects rather than mutating the shared config;
there is a test asserting `processorFields.grok_parser` still reads
`body`.
- This does not help pipelines already saved with a bare `body`. Preview
shows them making no change at all, with nothing explaining why —
surfacing that is a follow-up.
- Filters have the same problem and are not addressed here: `body
contains "x"` cannot match a map, and unlike a failing operator a
skipped filter produces no collector log. `queryBuilderToExpr` already
special-cases `body.<key>` for EXISTS; extending that to value
comparisons is the separate fix.
#### Description
- A referenced name in a trace query now resolves to a `LogicalField`
(#12499): one field, addressed by the requested spelling, backed by its
physical member keys. A semantic-convention family
(`deployment.environment.name` / `deployment.environment`) merges into
one expression with current-wins precedence; the response keeps the
requested spelling.
- `FieldMapper` gets one new method, `ExistsFor` (the per-key presence
primitive). `LogicalValueExpr` and `LogicalExistsExpr` build all family
SQL in one place from `FieldFor` and `ExistsFor`; no signal implements
family logic.
- Statement builders prefetch sibling spellings; the metadata store
stays family-blind and autocomplete stays literal. Traces and the
resource filter compile per logical field; logs, metrics, and the other
signals keep their SQL unchanged.
- The `resolve_semconv_families` feature flag (default: disabled) gates
all family behavior. With the flag off, the generated SQL is the same as
main; tests pin this. Part of #6143.
#### Additional Information
- Stack: #12441 (merged) → **#12442** → #12443 → #12444 → #12445 →
#12446 → #12447. This layer bases on main.
- Rollback: turn the flag off; stored telemetry is untouched.
#### Description
- Moves the service account role drawer off the deprecated nested
`/api/v1/service_accounts/{id}/roles` endpoints onto
`/api/v1/service_account_roles`, mirroring the earlier member →
`user_roles` migration.
- `useServiceAccountRoleManager` now reads role assignments from the
service account detail (`serviceAccountRoles` join rows), creates with
`{serviceAccountId, roleId}`, and deletes by the join-row id; the manual
query invalidation is dropped since the drawer already refetches the
same query.
#### Screenshots / Screen Recordings
https://github.com/user-attachments/assets/dd60e1d1-5d78-4f17-80f7-bb1a53730736
#### Description
- Moves the `serviceaccount` integration fixtures and suites off the
deprecated nested `/api/v1/service_accounts/{id}/roles` endpoints onto
`/api/v1/service_account_roles`.
- Roles are assigned via `POST /api/v1/service_account_roles` (201) and
revoked via `DELETE /api/v1/service_account_roles/{id}` (204), reading
join-row ids from the service account detail.
#### Additional Information
- Part of SigNoz/platform-pod#2919 — the integration-test half of the
consumer migration. The frontend migration and the deprecated-endpoint
removal are separate PRs.
#### Description
- Soft-deleting a user revoked the FGA grant but left the `user_role`
rows behind. The role-delete guard (`OnBeforeRoleDelete` →
`GetUsersByOrgIDAndRoleID`) still counted the deleted user, so the role
could never be deleted — and detaching the assignment was also blocked
because the user is deleted. That left the role permanently undeletable.
- `SoftDeleteUser` now deletes the user's `user_role` rows in the same
transaction that already clears its password, tokens, and preferences,
so the SQL side matches the FGA revoke.
- Migration `delete_orphan_user_roles` clears the orphan `user_role`
rows left by users deleted before this change.
#### Additional Information
- Regression test in `role/02_crud.py`: assign a custom role to a user,
delete the user, then delete the role → now `204` (was the deadlock).
#### Description
`RecentSearches.test.tsx` was failing intermittently on CI. Two separate
timing races, both in the test itself:
- Clicking a recent used `userEvent.click`, whose `pointerdown` blurs
the editor and closes the dropdown ~10ms later — before CodeMirror
applies the completion on `mousedown`. On a slow runner the dropdown was
already gone. Now uses `fireEvent.mouseDown`, which is what a browser
actually does here.
- `filters recents by substring as the user types` waited on a dropdown
that typing can close, with nothing to reopen it. All waits now
re-request completions if it closed.
No production code changed.
#### Issues closed by this PR
#### Screenshots / Screen Recordings
#### Additional Information
Ran the file 5x, the whole `QueryBuilderV2` directory 3x (211 tests),
and 3x under CPU load to mimic a slow runner — all green.
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
The other charts related to network uses binBps instead of `bytes`, and
similar to System Disk IO, we can use binBps since we are representing
throghutput
#### Screenshots / Screen Recordings (if applicable)
> Include screenshots or screen recordings that clearly show the
behavior before the change and the result after the change. This helps
reviewers quickly understand the impact and verify the update.
> The changes are on `Network Usage` and `System Disk Chart`, pay
attention to the Y unit.
Before:
<img width="1726" height="1090" alt="image"
src="https://github.com/user-attachments/assets/6a71f9fd-ae19-4ed8-bba1-3851ecf815f4"
/>
After:
<img width="1726" height="1091" alt="image"
src="https://github.com/user-attachments/assets/b3fc41dc-af20-4784-afe2-c3c1936ceba8"
/>
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic
closure on merge.
Closes https://github.com/SigNoz/pulse-pod/issues/211
---
### ✅ Change Type
_Select all that apply_
- [ ] ✨ Feature
- [ ] 🐛 Bug fix
- [x] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated: No
- Manual verification: Yes
- Edge cases covered: -
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: Infrastructure Monitoring - Hosts
- Potential regressions: None
- Rollback plan: Revert this commit
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Feature |
| Description | We updated the charts of Network IO and System disk IO
to use unit of bytes per second instead of bytes. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
- Clears all 19 high-severity advisories reported by `pnpm audit` (24
findings → 4, none high).
- Most of this is security floors in `pnpm-workspace.yaml`, following
the file's existing capped-override convention — each entry records the
vulnerable path and what would let us drop it again.
- `brace-expansion` needs three separate entries because three majors
coexist in the tree: minimatch@3 (via `test-exclude`), minimatch@9 (via
jest's `glob@10`), and minimatch@10 (via `eslint-plugin-sonarjs`).
- `image-size` has no patched release at all, so the only fix is
dropping the dependency — `less@4.5.0` removed it, and that lands inside
`typescript-plugin-css-modules`' `^4.2.0` range.
- `postcss` 8.5.14 → 8.5.26 is the one direct bump; it's a direct
devDep, so a floor override would only hide a stale version in
`package.json`.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes -
https://github.com/orgs/SigNoz/projects/39/views/20?pane=issue&itemId=230120135&issue=SigNoz%7Cengineering-pod%7C5925
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- Deliberately scoped to highs. The 4 remaining moderates (`dompurify`,
`@remix-run/router`, and two `react-router` advisories) are left for a
follow-up.
<!--Please delete paragraphs that you did not use before submitting.-->
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
Prevent the error message to overflow outside the table.
#### Screenshots / Screen Recordings (if applicable)
> Include screenshots or screen recordings that clearly show the
behavior before the change and the result after the change. This helps
reviewers quickly understand the impact and verify the update.
Before:
https://github.com/user-attachments/assets/d53845ed-7db9-4451-a2ca-31bc6127ec31
After:
https://github.com/user-attachments/assets/78e1e2d4-a5e1-46d7-9f66-117d1868e9f5
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic
closure on merge.
Closes https://github.com/SigNoz/pulse-pod/issues/222
---
### ✅ Change Type
_Select all that apply_
- [ ] ✨ Feature
- [x] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated: No
- Manual verification: Yes
- Edge cases covered: -
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: Infrastructure Monitoring
- Potential regressions: -
- Rollback plan: Revert this commit
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Bug Fix |
| Description | We updated the error layout to ensure it won't overflow
the table in case the APIs fail with a large message. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
Renders the new `DataViewer` (Pretty tree + JSON) inside the V2
log-details Overview tab
Wires up filter / group-by on each attribute similar to pretty view in
trace details
**Change points**
**Rendering the View**
- Data Viewer renders using `aggregateAttributesResourcesToObject` which
is not written fresh, just extracted from an existing logic
`aggregateAttributesResourcesToString`.
- jsonData is separately sent to the DataViewer as rendering logic for
pretty and json view is different in this case. unlike trace details
where both views had same source data.
**Group by / FIlter and other logic**
- All the related logic resides in 2 major files:
`useLogAttributeActions.tsx` and `logAttributeActions.utils.ts`.
- we build the fieldKey ourselves (`buildLogFilterTarget`) as this is
now different from old representation.
- filter / group by / replace build the query locally now...we do not
make the `getAggregateKeys` call at all. we fabricate the telemetry
field key ourselves with just the name and dataType filled and rest kept
empty. so no prefetch, no resolver, no loader.
- the query building is extracted into 3 utils: `getFilterQueryData`,
`getGroupByQueryData`, `getReplaceFilterQueryData`. the hook just calls
these over `updateQueriesData`.
- restricted fields apply for body as well: `timestamp` / `id` / `date`
inside body no longer show filter / group by. reuses
`RESTRICTED_SELECTED_FIELDS`.
**Pretty View**
- renderLeafValue: introduce to render custom leaf value. we are using
this here for body. This is extensible for other usecases as well...like
showing md format leaf for LLMs in the future.
- fixed leaf vs nested row indentation so keys line up at every depth.
**Other changes**
- Filter value keeps its data type: dataType is threaded through so a
numeric/bool filter value stays unquoted.
- Removed the redundant outer JSON tab in V2.
#### Screenshots / Screen Recordings (if applicable)
https://github.com/user-attachments/assets/ec1f0842-0a3b-407b-807c-0c3b0f9ed86a
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic
closure on merge.
Closes: https://github.com/SigNoz/engineering-pod/issues/4618
Closes: https://github.com/SigNoz/engineering-pod/issues/4630
Closes: https://github.com/SigNoz/engineering-pod/issues/5781
---
### ✅ Change Type
_Select all that apply_
- [x] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
## Pull Request
---
### 📄 Summary
- Add a `type` param to `/api/v1/fields/keys`; for type=builder_ai_query
(flag-gated) the metadata store returns the per-trace aggregate columns
(llm_call_count, input_tokens, …) as
trace-context keys — they're computed at query time, never ingested, so
the attribute scan can't serve them.
- Split `TraceColumn.Orderable` into `Orderable + Filterable`: ORDER BY
uses orderable, the trace-level filter validates against filterable, and
the API only returns keys that are both. `last_activity_time` is
order-only and now rejected in filters with a targeted error.**
- UI note: last_activity_time should be added to client-side list (it's
the default sort).
#### Issues closed by this PR
Part of https://github.com/SigNoz/engineering-pod/issues/5714
---
### ✅ Change Type
_Select all that apply_
- [x] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated: ✅
- Manual verification: ✅
- Edge cases covered: ✅
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: None
- Potential regressions:
- Rollback plan:
## Pull Request
We have issues in TraceDetailsV3:
- When you open TraceDetailsV3 and click on spans, if you keep clicking
on these spans, it will change the URL. When you click on Go Back, it
will just navigate you through the history of URLs you have clicked,
which is not the right experience.
- The same thing happens if you have opened span details, The drawerless
modal on the right-hand side: if you close it and click on the Back
button, it will just open that drawer once again.
### 📄 Summary
#### Screenshots / Screen Recordings (if applicable)
https://github.com/user-attachments/assets/a60f544c-83ea-4f06-9233-8fc722428a04
#### Issues closed by this PR
Closes -
Before -
https://github.com/orgs/SigNoz/projects/39/views/11?filterQuery=assignee%3Atewarig&pane=issue&itemId=223289782&issue=SigNoz%7Cengineering-pod%7C5851
Now -
https://github.com/user-attachments/assets/ec28925b-c61a-43f1-b01e-510fab7c5a62
---
### ✅ Change Type
_Select all that apply_
- [ ] ✨ Feature
- [x] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🐛 Bug Context
#### Root Cause
We are pushing span click as well as when the span detail modal closes
and opens to the history. Ideally, we should just replace it.
#### Fix Strategy
Pass `{ replace: true }` to `safeNavigate` at both call sites. Every
route that mutates `spanId` in trace details now replaces rather than
pushes:
| Site | Trigger | Before | After |
|---|---|---|---|
| `Success.tsx:693` | waterfall span click | push | **replace** |
| `index.tsx:83` | close span details panel | push | **replace** |
`useCopySpanLink` also builds a `spanId` URL but only writes it to the
clipboard — it never navigates, so it is correctly untouched.
---
### 🧪 Testing Strategy
- **Tests added/updated:** `UnifiedSpanClick.test.tsx`
I have tested manually.
---
### ⚠️ Risk & Impact Assessment
- **Blast radius:** Small and contained. Two one-line changes, both
inside `pages/TraceDetailsV3`. No API, schema, or shared-utility
changes. Nothing outside trace details reads or writes the `spanId`
param.
- **Rollback plan:** Revert the commit. There is no state, migration, or
persisted data involved, so a revert fully restores the prior behaviour
with no cleanup.
---
### 📝 Changelog
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Bug Fix |
| Description | The browser Back button on the trace detail page now
returns you to the page you came from, instead of stepping back through
each span you had clicked within the trace. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [ ] Manually tested
- [x] Breaking changes documented
- [x] Backward compatibility considered
---
## 👀 Notes for Reviewers
Two smaller notes:
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
### Description
Old saved views still store `selectedFields` as `key`/`dataType`/`type`.
That shape unmarshals cleanly into a zero-valued `TelemetryFieldKey`, so
migration 111 saw no error and skipped those rows — they now read back
with an empty `name`, which breaks the explorer UI.
- Migration 113 remaps `key` → `name`, `type` → `fieldContext`,
`dataType` → `fieldDataType`, including the old spellings with no
current alias (`spanSearchScope`, `array(string)` and friends). Entries
with neither `name` nor `key` are dropped; valid entries are left
untouched.
- `SavedViewSpec.Validate` now requires `selectedFields[].name`, so this
can't be written again.
Closes https://github.com/SigNoz/engineering-pod/issues/5909
#### Description
- Splits testify usage in the existing alert channel tests (email,
slack, pagerduty, opsgenie, msteamsv2, webhook) per the convention
established in #12314: `require` for error checks and guards before
indexing/dereferencing, `assert` for the independent value checks so one
failure doesn't mask the rest.
- Fixes illegal `require`/`t.Fatal` calls inside `httptest` handlers
(pagerduty, slack), which run on the server's goroutine where `FailNow`
must not be called; these now use `assert`.
- Adds missing guards before unchecked indexing and pointer dereferences
(opsgenie request slice, msteamsv2 blocks, slack field pointer, email
HTML/Text pointers).
- Normalizes leftover raw `t.Fatal`/`t.Errorf` and redundant `if err !=
nil { require.NoError }` patterns to plain testify calls.
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#164
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
This PR prevents `@monaco-editor/react` from loading the core
monaco-editor package from a third party CDN. This fixes an issue where
the CDN is blocked for certain users/tenants, preventing monaco-editor
from loading.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
closes https://github.com/SigNoz/engineering-pod/issues/5871
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
Pager: https://signoz-1.pagerduty.com/incidents/Q108N2EUVQAJN2
Sentry: https://signoz-io.sentry.io/issues/7583880805/
<!--Please delete paragraphs that you did not use before submitting.-->
## Pull Request
---
### 📄 Summary
Large numbers in dashboard panels rendered as an undelimited digit run —
`1234567` instead of `1,234,567` — which is hard to read at a glance and
hard to compare between panels.
Unitless values were the visible gap. They format through the `'none'`
unit, which routes to `formatDecimalWithLeadingZeros`, whose
`Intl.NumberFormat` is constructed with `useGrouping: false`. Units that
scale their own value (`bytes` → `1.18 MiB`, `short` → `1.23 Mil`) never
reach four integer digits, so they never showed the problem.
The grouping is applied inside `formatPanelValue` — the single seam
through which V2 panels reach `getYAxisFormattedValue` — rather than at
one call site. That is deliberate: readability of a large scalar is not
specific to one panel kind, so the Number panel, Table value cells and
the threshold-row previews all pick it up from one place instead of each
opting in.
`groupThousands` itself is conservative. It touches only the first
numeric token's integer digits, so fractions, unit labels and
formatter-scaled values pass through untouched, and exponent notation is
skipped (grouping a mantissa reads as noise).
#### Screenshots / Screen Recordings (if applicable)
No capture attached. The visible delta is purely the separators:
| Panel | Unit | Before | After |
|---|---|---|---|
| Number | — | `1234567` | `1,234,567` |
| Number | `percent` | `1234567%` | `1,234,567%` |
| Number | `bytes` | `1.18 MiB` | `1.18 MiB` (unchanged) |
| Table cell | — | `1234567` | `1,234,567` |
#### Issues closed by this PR
Closes#7669
---
### ✅ Change Type
_Select all that apply_
- [x] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🐛 Bug Context
**N/A** — this is an enhancement, not a regression. Grouping was never
implemented; `useGrouping: false` in `formatDecimalWithLeadingZeros` is
longstanding and intentional for the axis-tick path it was written for.
---
### 🧪 Testing Strategy
- **Tests added/updated:**
- `groupThousands.test.ts` (new) — the transform in isolation.
- `parseFormattedValue.test.ts` (new) — this util had no suite; covers
the unit split, including grouped input.
- `formatPanelValue.test.ts` — asserts grouping at the seam, and that
unit-scaled values stay ungrouped.
- `NumberPanel/__tests__/Renderer.test.tsx` — grouped value, grouped
value + separate unit, and unit-scaled value left alone.
- `TablePanel/__tests__/Renderer.test.tsx` — pins the grouping that
Table cells inherit. Worth noting for reviewers: `tableColumns.test.ts`
and `tableCsv.test.ts` both stub `formatPanelValue`, so they are blind
to this change by construction — the renderer test is what actually
covers the Table path.
- **Manual verification:** not a click-through of a live dashboard.
Instead: the full frontend jest suite (7,312 passing; the 3 failures are
in unrelated suites — `QuerySearch`, `AuthDomain` — and were confirmed
flaky/pre-existing by re-running them alone and against a stashed
pristine tree), plus `tsgo --noEmit`, `oxlint`, `oxfmt --check` and
`vite build` all clean. The real `getYAxisFormattedValue` output was
probed directly for 11 value/unit combinations before writing the
transform, and `papaparse.unparse` was run directly to confirm exactly
how a grouped cell serializes.
- **Edge cases covered:** negative values (sign stays outside the first
group), fractions (never grouped), exponent notation (skipped), `∞` /
`-∞` / `NaN` (untouched), prefix and suffix unit decoration (`$
1,234,567`, `1,234,567%`, `1,234,567 ms`), formatter-scaled units,
values below 1000, zero, and idempotency on already-grouped input.
---
### ⚠️ Risk & Impact Assessment
- **Blast radius:** every `formatPanelValue` consumer — the Number
panel, Table panel value cells, the three threshold-row previews in the
config pane, and the Table CSV export. Display-only in all cases; no
spec/DTO or API change, nothing persisted.
- **Potential regressions:**
- **The CSV export is the one behavior change worth a reviewer's
attention.** `formatTableCellText` is shared by the Table renderer and
the export, so a unitless numeric column now serializes as `"1,234,567"`
(papaparse quotes any field containing the delimiter) instead of
`1234567` — spreadsheet `SUM`/`AVG` and downstream `parseFloat` would
read it as text. Columns with a unit were *already* display-text in the
export (`295.43 ms`, `1.18 MiB`) by design ("reusing the on-screen cell
formatting", V1 parity), so only unitless numeric columns change in
kind. Called out explicitly because it is an accepted trade-off, not an
oversight — if we would rather keep the export numeric, the contained
fix is a flag threaded through `formatTableCellText` from `tableCsv.ts`
only, leaving the render path grouped.
- Table **sorting and threshold evaluation are unaffected** — both read
`toCellNumber(raw)`, never the formatted string.
- `parseFormattedValue` had to learn to accept `,`, otherwise a grouped
value would fall through to the whole-string fallback and lose its unit
split. Covered by its new suite.
- **Rollback plan:** revert the PR. Display-only with no migration or
persisted state, so a revert is immediate and total.
---
### 📝 Changelog
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Feature |
| Description | Large numbers in dashboard panels are now formatted with
thousand separators (`1,234,567`), in the Number panel, Table panel
value cells and threshold labels. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [ ] Manually tested
- [x] Breaking changes documented
- [x] Backward compatibility considered
---
## 👀 Notes for Reviewers
Two things I would want a second pair of eyes on:
1. **"Manually tested" is deliberately unchecked.** This was validated
through tests and direct probes of the real formatter, not by clicking
through a running dashboard. A quick look at a Number panel and a Table
panel — plus a screenshot for this PR — is worth doing before merge.
2. **The CSV trade-off** under Risk & Impact. Grouping at the seam is
what makes the change one line instead of four call sites, but the
export rides the same path. The narrower alternative is described there
if you would rather not accept it.
The three commits are independently reviewable: the transform, the
parser tolerance it requires, then the seam that turns it on.
#### Description
- The collector gets a `normalize` pipeline prepended ahead of user
pipelines when `use_json_body` is on — injected in
`RecommendAgentConfig` and delivered over opamp — which parses the log
body into JSON. Preview simulated only the user's pipelines, so a
pipeline authored against `body.<field>` behaved differently in preview
than in production, and one written against `body` looked fine in
preview while doing nothing on real logs.
- Preview now evaluates the flag for the caller's org and prepends the
same pipeline, so what it shows is what the collector does.
#### Issues closed by this PR
Fixes https://github.com/SigNoz/engineering-pod/issues/5897
#### Additional Information
Verified end to end against a local stack — devenv ClickHouse, a
collector with `body_json_enabled` connected over opamp, `use_json_body`
on — by driving the two calls the preview screen makes: sample logs,
then preview with those logs. `parse_from: body.message` extracts
attributes; `parse_from: body` extracts nothing, matching what the
collector does with a normalized body.
Log bodies render as stored rather than unwrapped, so what you see is
what the pipeline operates on.
Needs SigNoz/signoz#12534 to pick sample logs by body — without it the
v3 query behind the sample-log list errors for these orgs.
SigNoz/signoz#12535 stacks on this to surface the collector's own
explanation when an operator cannot parse a log.
A filter on a metrics label that isn't in metadata ran silently: the
query fell back to reading the label directly, but nothing told the user
the key was unknown. Removes the `TODO(srikanthccv)` in the metrics
statement builder.
### What
The detection was already written, and already in the right place.
`conditionBuilder.ConditionFor` spots a filter key with no metadata
match, warns, and synthesizes an attribute-context key so the query
still runs — and it only ever sees terms in **key position**, so it
cannot mistake a value or a dashboard variable for a key. That is
exactly what the TODO was waiting for.
Two things hid it:
- `Build` pre-seeded the field-key map with a synthesized entry for
every lexer-derived selector, so `MatchingFieldKeys` always matched and
the missing-key branch was dead code.
- The metrics builder never read `PrepareWhereClause`'s warnings — the
visitor collects them and `unionStatements` merges them, but nothing
ever set them.
So: drop the pre-seeding, and carry the warnings out of
`buildTimeSeriesCTE` onto the statement.
### Notes
- **Generated SQL is unchanged.** The key the condition builder
synthesizes (attribute context, name as written) is what the pre-seeding
was injecting, so `test_missing_key_falls_back_to_labels` still expects
byte-identical SQL and only gains the warning.
- A full-text term routes through `labels`, which is a real column, so
it takes the `isColumn` branch and stays silent — bare-word searches
don't start warning.
- The reduced statement prepares the same filter over the same keys, so
only the main path's warnings go into the union; carrying both would
show each warning twice.
### Testing
- `test_missing_key_falls_back_to_labels` gains the expected warning,
same SQL.
-
`queriermetrics/10_key_resolution.py::test_metrics_filter_unknown_label_matches_nothing`
now asserts the warning instead of asserting silence.
- `queriermetrics/02_warnings.py` already covered the TODO's own example
(`my_tag = $tag`). It passed before only because every warning was
suppressed; it is now a real guard that a value-position variable is not
flagged.
- `queriermetrics` integration suite: 118 passed. `go test
./pkg/statementbuilder/... ./pkg/telemetryschema/...` green. `make
go-lint` and `make py-lint` clean.
#### Description
- Moves the endpoints to `/api/v2/auth_domains` and removes the
`/api/v1/domains` routes — the request/response shapes changed, so they
live behind new paths instead of breaking v1 in place.
- Restructures the auth domain payload: `config` is now a `{kind, spec}`
discriminated envelope (same pattern as `RuleThresholdData` /
`EvaluationEnvelope`), replacing the old `ssoType` discriminator with
`samlConfig` / `googleAuthConfig` / `oidcConfig` sibling fields;
`ssoEnabled` and `roleMapping` move to the root as `enabled` and
`roleMapping`.
- Renames the provider kind `google_auth` → `google`, and the SAML keys
to metadata-consistent ones: `samlEntity` → `entityId`, `samlIdp` →
`location`, `samlCert` → `certificate`.
- Migrates the persisted documents too: a new sqlmigration rewrites
`auth_domain.data` into `{enabled, config: {kind, spec}, roleMapping}`,
so all legacy-shape code (storable twins, `google_auth` translation,
per-kind conversion switches) is deleted; the remaining per-kind wiring
lives in a single variant registry that `UnmarshalJSON`,
`JSONSchemaOneOf` and the discriminator mapping derive from.
- `AuthDomain` exposes the domain shape (`Enabled()`, `Kind()`,
`Config()`, `RoleMapping()`, typed spec accessors) instead of the
persisted document; `config` presence is enforced explicitly on
Postable/Updatable (the old PUT path never enforced it and could poison
a row).
- Secret fields (`clientSecret`, `serviceAccountJson`) are `format:
password` in the schema, and `GoogleConfig` loses the unused
`redirectURI` (the migration strips it from persisted documents).
- Frontend: regenerated client is a clean discriminated union; both
directions of the envelope↔form translation live in
`CreateEdit.utils.ts` with an explicit kind→provider mapping (no
cross-enum casts).
- The generated OpenAPI spec carries a real `discriminator`; the
kind/spec envelope pattern itself is documented generically in #12494,
and this PR only keeps the auth domain worked example in `types.md` in
step with the refactored types.
- Updates the google authn integration tests (#12486) to the new API,
and adds parametrized POST→GET roundtrip cases pinning the response
contract per kind (server-side defaulting, role-name normalization, null
maps) plus enforcement-toggle update coverage.
#### Issues closed by this PR
ClosesSigNoz/platform-pod#2268
#### Additional Information
- Breaking change: `/api/v1/domains` is gone; the resource is now
`/api/v2/auth_domains` with the new shape. Login and SSO callback flows
are behaviorally unchanged, and existing rows are migrated in place at
startup.
- The `AuthNProvider` rename also surfaces in `/api/v2/sessions/context`
responses (`provider: "google"`) — the login page only consumes the
callback `url` — and in the reported stats key, which changes from
`authdomain.google_auth.count` to `authdomain.google.count`.
- Verified: `make go-test`, Go lint, frontend jest suites for
AuthDomain, `pnpm build`, `pnpm tsgo --noEmit`, and the full
`callbackauthn` domain suites (17 tests: roundtrip pins, the enforcement
toggle, and the google E2E flows) against a container rebuilt from this
branch — including a live run of the data migration over legacy-format
rows.
A metric label may be named after a column the generated query builds
for itself, and the metrics and meter builders selected group-by columns
under the label's own name — so `group by ts` or `group by value`
produced SQL with two columns of that name, which ClickHouse rejects.
### What
- Metrics and meter now alias group-by columns
`__GROUP_BY_KEY_<i>_<name>`, the scheme the logs and traces statement
builders already use.
- `pkg/querier/consume.go` already strips that prefix on all three read
paths (time-series, scalar, raw), so API responses are unchanged.
- Metrics' `ColumnExpressionFor` now returns the bare expression like
the logs and traces mappers. It was the only one returning an aliased
expression (`expr AS <name>`), which `agg_rewrite.go` splices inside a
function argument — giving `sum(expr AS <name>)` if metrics ever grows
expression aggregations. Callers alias and escape, as logs does.
- The histogram pipeline derives its CTE-side query once — `le` appended
last, plus the existing rate/sum rewrite — instead of mutating the query
and restoring it around the whole pipeline. The final select takes the
original minus `le`, so the remaining keys hold the positions their CTE
aliases were built from.
With a label named `ts`, before:
```sql
SELECT ts, `ts`, multiIf(…) … GROUP BY fingerprint, ts, `ts`
```
and after:
```sql
SELECT ts, `__GROUP_BY_KEY_0_ts`, multiIf(…) …
```
A label named `value` was the quieter case — the spatial CTE selected it
next to the aggregate of the same name:
```sql
SELECT ts, `value`, sum(per_series_value) AS value …
```
### Notes
- Meter comes along because it holds a
`*metricsstatementbuilder.StatementBuilder` and calls the shared
`BuildFinalSelect`; aliasing metrics alone would leave meter ordering by
an alias its own select never produced. `GroupByColumnAlias` /
`GroupByAliases` are exported for it, alongside the `GetKeySelectors` /
`RateTmpl` already shared across that boundary.
- Meter had the identical collision, so this fixes it there too.
### Testing
- `TestGroupByAliasAvoidsColumnCollision` covers `ts`, `value`,
`fingerprint` and an ordinary label, in both the metrics and meter
builders; all three collision cases fail without the change.
- `reduced_test.go` gains `histogram_p99_group_by` and
`gauge_avg_avg_group_by` — the reduced path had no group-by coverage at
all, so neither the aliases in its four CTE builders nor the union's
`ORDER BY` were exercised. The histogram case pins that both `UNION ALL`
branches emit the same columns.
- `test_histogram_count_no_param` pins the `SELECT *` branch, where `le`
stays unaliased so `ORDER BY toFloat64(le)` resolves.
- Both new behaviours were mutation-checked: appending `le` first
instead of last, and returning the bare name from `GroupByColumnAlias`,
each turn the relevant tests red.
- Twelve expected-SQL blobs regenerated across the metrics and meter
statement builder tests — alias-only diffs.
- `go test ./...` green, `make go-lint` clean.
Fixes https://github.com/SigNoz/engineering-pod/issues/5868
#### Description
- Resetting a password via a reset token left existing sessions valid.
Changing a password voluntarily already revoked them; the reset path now
does the same.
#### Issues closed by this PR
closes: SigNoz/platform-pod#2667
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
Instead of generic phrase, we created a single line summary for each
chart title
#### Screenshots / Screen Recordings (if applicable)
> Include screenshots or screen recordings that clearly show the
behavior before the change and the result after the change. This helps
reviewers quickly understand the impact and verify the update.
Before:
<img width="1755" height="1269" alt="image"
src="https://github.com/user-attachments/assets/5d68bdde-1dd8-4405-ba68-90c89e685d50"
/>
After:
<img width="1756" height="1269" alt="image"
src="https://github.com/user-attachments/assets/003e2740-e25c-4cd0-8b3c-4e28959e194a"
/>
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic
closure on merge.
Closes https://github.com/SigNoz/pulse-pod/issues/196
---
### ✅ Change Type
_Select all that apply_
- [x] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated: No
- Manual verification: Yes
- Edge cases covered: -
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: Infrastructure Monitoring - Details
- Potential regressions: None
- Rollback plan: Revert this commit
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Feature |
| Description | We updated the documentation for each chart title to
include a single line summary to give you a brief explanation about each
chart. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
## Pull Request
---
### 📄 Summary
This is PR ads basic happy flow E2E for our LLM observability flow.
Since most of the cases are covered by the integration test itself, we
have added happy path here.
We have also added test for Test tab here.
So we have added an integration test for a test endpoint here.
#### Issues closed by this PR
Closes - https://github.com/SigNoz/engineering-pod/issues/5764
---
### ✅ Change Type
_Select all that apply_
- [x] 🧪 Test-only — E2E + integration coverage for LLM Observability
- [x] 🛠️ Infra / Tooling — E2E helpers + e2e-scoped feature-flag
conftest
---
### 🧪 Testing Strategy
- **Tests added:**
- **E2E (Playwright):**
`tests/e2e/tests/llm-o11y/attribute-mapping.spec.ts`,
`tests/e2e/tests/llm-o11y/llm-pricing.spec.ts` +
`tests/e2e/helpers/{attribute-mapping,llm-pricing}.ts`. Both specs are
`test.describe.configure({ mode: 'serial' })` and tear down what they
create via the API.
- **Integration (Jest/RTL/MSW):** `TestTab/__tests__/TestTab.test.tsx` —
4 cases:
| Case | Time |
|---|---|
| runs the sample span through the mappers and renders the populated
result | 447 ms |
| surfaces a backend error and renders no results | 122 ms |
| persists an edited span to local storage and restores it on remount |
657 ms |
| resets to the sample span and clears the persisted input | 70 ms |
- **Manual verification:** full LLM Observability Jest suite is green
locally — **7 suites / 70 tests passed in 24.3 s** (`npx jest --verbose
src/container/LLMObservability`), including the 4 new Test-tab cases.
- **Edge cases covered:** Test-tab backend 500 → error surfaced and no
results rendered; span input persisted across remount and cleared on
reset; E2E fixture cleanup is failure-tolerant (`.catch()` on
list/delete so a broken run doesn't mask the real assertion failure).
- **Not yet captured:** a fully-green E2E run in CI — the e2e workflow
only fires on PRs carrying the `safe-to-e2e` label (see Notes).
---
### ⚠️ Risk & Impact Assessment
- **Blast radius:** none in product code. No `frontend/src` runtime
file, no Go package, and no shared pytest fixture is modified — the only
non-test-file change is the new `tests/e2e/conftest.py`, scoped to the
`e2e` package.
- **Potential regressions:** limited to CI surface.
`tests/e2e/conftest.py` overrides the package-scoped `signoz` fixture
with a distinct `cache_key`, so it brings up **one additional container
set** for the e2e package rather than mutating the shared one —
integration suites keep the stock feature set.
- **Rollback plan:** revert the PR. No schema/migration changes, no
runtime behaviour to unwind.
---
### 📝 Changelog
| Field | Value |
|------|-------|
| Deployment Type | N/A |
| Change Type | Maintenance |
| Description | N/A — test-only, no user-facing change. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested (LLM Observability Jest suite green: 7 suites / 70
tests; E2E specs exercised against a local flag-enabled stack)
- [x] Breaking changes documented (none)
- [x] Backward compatibility considered
---
## Notes for Reviewers
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- Remove all 34 v1 infra-monitoring routes:
`/api/v1/{hosts,processes,pods,nodes,namespaces,clusters,deployments,daemonsets,statefulsets,jobs,pvcs}/{list,attribute_keys,attribute_values}`
and `/api/v1/infra_onboarding/k8s/status`. The frontend is fully on
`/api/v2/infra_monitoring/*` (checks supersedes the onboarding-status
endpoint).
- Delete the now-dead implementation: `pkg/query-service/app/infra.go`,
the whole `pkg/query-service/app/inframetrics` package, and
`pkg/query-service/model/infra.go` (~8.2k lines).
- Drop `Reader` methods only used by the removed code:
`GetCountOfThings`, `GetActiveHostsFromMetricMetadata`,
`GetMetricsExistenceAndEarliestTime`.
- Delete the last dead frontend caller
`src/api/infraMonitoring/getHostLists.ts` (missed by #12415).
#### Issues closed by this PR
Part of SigNoz/pulse-pod#246
#### Additional Information
- `/api/v1/processes/list` is removed without a v2 equivalent — it was
never shipped in the UI.
- The per-entity `attribute_keys`/`attribute_values` endpoints are
superseded by the generic fields/autocomplete APIs, which the infra
pages already use.
- Verified locally: full build, and the `inframonitoring` integration
suite passes 580/580.
#### Description
- Removes the deprecated user endpoints that now have v2 replacements:
- `POST /api/v1/invite`, `GET /api/v1/user`, `GET
/api/v1/getResetPasswordToken/{id}`, `POST /api/v1/resetPassword`
- `POST /api/v2/users/{id}/roles` and `DELETE
/api/v2/users/{id}/roles/{roleId}`, superseded by `/api/v2/user_roles`
- `GET /api/v1/user/me` stays registered but returns 501 pointing at
`GET /api/v2/users/me`, following the v1 dashboard endpoints.
- Drops the handlers, module methods and types that only existed to
serve them (`DeprecatedUser`, the `user_invite` types, `PostableRole`).
- Moves the four remaining `*_cleanup` teardown tests off `DELETE
/api/v2/users/{id}/roles/{roleId}` onto `DELETE
/api/v2/user_roles/{id}`. Removal is keyed by the `user_role` entry id,
so they read it from `GET /api/v2/users/{id}` — that endpoint is not
deprecated and its other uses are untouched.
- Regenerates `docs/api/openapi.yml` and the frontend client. No
hand-written frontend code referenced the removed operations.
#### Issues closed by this PR
Closes: https://github.com/SigNoz/platform-pod/issues/2667
#### Additional Information
- `/api/v1/user/me` is a stub rather than a deletion because an
unregistered `/api/*` path falls through to the SPA catch-all and
answers 200 with `index.html`, which older mcp reads as a successful
response — a 501 fails loudly instead.
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
This PR aims to fix the issues we have with translation, today when we
enable the translation, some parts of the app crash due to how the react
works and how the translation works.
In simple works, if you have: `<button>{(var ? 'text' : 'other text')}
{icon}` it will crash because the React will represent the `text` and
`other text` as `TextNode`, and when translate is performed, it changes
the parent of this element to `font` and causes the react to be "blind",
and when trying to delete the element, it cannot find.
> Read
https://martijnhols.nl/blog/everything-about-google-translate-crashing-react
to understand more
There's many fixes that includes ignore the errors and let the app with
invalid data, or actually go ahead and find the places with this pattern
and avoid them.
I kinda mixed two approaches, I introduced a new plugin based on
https://github.com/getcouped/eslint-plugin-react-google-translate/ but
adapted a little bit for our necessity and for our codebase (with oxc).
If we only use this plugin to find and fix the places, we will find most
of the issues crashing the app, but not all of them.
Why not all? Because even our component library is not safe enough for
google translate, eg: https://github.com/SigNoz/components/issues/351
So, I also included https://npmx.dev/package/translation-resilience,
this lib has another approach to fix the issue with the TextNode:
```
Instead of swallowing errors, this shim puts the original text nodes **back** the moment the renderer touches them:
1. A document-wide `MutationObserver` recognizes translation's displacement pattern (merge, wrap, remove — a pattern renderer commits never produce) and tracks each replaced text run as a *displacement group*: the ordered renderer-owned originals with their pre-translation values, plus the wrapper nodes currently standing in for them.
2. Patched `Node.prototype.removeChild` / `insertBefore` / `appendChild` and the `nodeValue` / `data` setters detect operations on displaced text nodes and first **restore the group** — originals go back into the wrappers' position, wrappers are removed — then let the native operation proceed on a consistent tree.
3. The translator's own observer notices the restored (now updated) text and re-translates it, so the user sees fresh, translated content. The loop is self-healing: update → restore → re-translate.
The result: no crashes, **and** live data keeps updating on translated pages — in the visitor's language.
```
We could keep the lib only and no plugin? Yes, but I want to make our
app more resilient without need the help of the lib, so we can continue
to adopt/fix places that has the pattern to crash the app, and
eventually, we can remove the lib because our app is resilient enough.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/platform-pod/issues/2912
<!--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/0225464b-1afe-46ad-afe7-25f79e25201a
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
This lib has a performance cost but the lib only enable itself when it
detects the translation is enabled, so our app (and users) should not
see/perceive any performance cost due to this lib. But again, this is
another reason to slowly adapt and fix all places that offers a
potential problem to google translate.
<!--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
- Adding a few values to **Group By** made the tags wrap onto a second
row that rendered outside the field, on top of the add-on toggles below
it. **Order By** and the formula Order By row had the same bug.
- The add-on field pinned the antd select and its selector to `height:
36px`, so it could never grow. Both are `min-height: 36px` now —
single-line selects keep the same 36px row, tag selects grow with their
rows.
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
Before
<img width="1742" height="117" alt="image"
src="https://github.com/user-attachments/assets/12dfccbe-d087-4857-9bd0-3b6dfa0a1da1"
/>
After
<img width="1778" height="164" alt="image"
src="https://github.com/user-attachments/assets/abeb0b38-b0ce-4749-bf1c-32fc865833b9"
/>
#### Issues Closed
Closes https://github.com/SigNoz/pulse-pod/issues/270
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- Broke in #11992, which put `height: 36px` on `.ant-select-selector`
and moved the field's border onto it. The same `height` on the root
`.ant-select` predates that but never applied, because
`GroupByFilter`/`OrderByFilter` pass an inline `height: 100%` — antd was
left to size the selector from its content, so the field used to grow as
tags wrapped.
- Checked in a headless-Chromium repro of the field using antd 5.11's
select rules: 12 tags in a ~900px field hang 20px below the box on
`main`, and sit inside it with this change.
#### Description
Two commits: a clean revert of #12523, then a reland with the body
stringified.
**Why the revert.** #12523 selected `body_v2 as body` for orgs on JSON
bodies. ClickHouse resolves identifiers in `WHERE` against SELECT
aliases, and the v3 filter builder emits a bare `body` (`body != ''` for
exists, `lower(body) like …` for contains), so every body filter started
running against the JSON column and failed with `Code: 117 … Cannot
parse JSON object here: while converting '' to JSON`. The pipelines
preview always sends the pipeline's filter, so picking sample logs by
body errored outright.
**What the reland changes.** The select is `toString(body_v2) as body`,
so the alias stays a String and those filters compare against the body
text again. As a bonus they now actually match — before #12523 they ran
against the legacy `body` column, which the collector writes empty for
these orgs, so they silently matched nothing. The JSON column decoding
#12523 added to `GetListResultV3` is not relanded: nothing selects a
JSON column on this path now, and it failed the entire query on a row it
could not unmarshal rather than just that row.
Reproduced directly against ClickHouse:
```sql
SELECT body_v2 AS body FROM signoz_logs.distributed_logs_v2 WHERE body != '' LIMIT 1;
-- Code: 117. DB::Exception: Cannot parse JSON object here: while converting '' to JSON(...)
SELECT toString(body_v2) AS body FROM signoz_logs.distributed_logs_v2 WHERE body != '' LIMIT 1;
-- {"level":"error","message":"json log line","user":"alice"}
```
#### Additional Information
Verified end to end on a local stack (devenv ClickHouse + a collector
with `body_json_enabled`, `use_json_body` on): `body EXISTS` and `body
CONTAINS` both return rows, and the body comes back as the stringified
JSON.
v5 is unaffected — its field mapper builds a real JSON expression
instead of emitting a bare `body`, so `body EXISTS` there already worked
and still returns object bodies.
The response shape for v3 is a JSON string rather than the object #12523
returned. Consumers that need structure can parse it; the pipelines
preview endpoint accepts either, since it types the log body as `any`
and re-parses through the `normalize` pipeline.
Known gaps left alone, since they predate this or need the filter
builder to become JSON-aware: aggregation and group-by queries still
read the empty legacy `body` column (only the list select carries the
alias), body filters cannot use the `body_v2` skip indexes while
stringified, and the v4 endpoint never sets the flag.
#### Description
Adds integration tests for the Recent Searches dropdown in the query
builder search editor.
What's covered:
- A saved recent shows up under "Recent searches" on focus
- Recents filter by substring as you type
- Recents stay partitioned by signal — a `traces` recent never leaks
into the `logs` editor
- A recent identical to what's already typed is excluded
- Clicking a recent applies the whole expression and closes the popup
- The dropdown caps at `RECENTS_DISPLAY_CAP` entries, newest first
(asserted as a full ordered array)
- The per-entry delete button removes the recent from both the dropdown
and the store, without applying it
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5649
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
Instrument the span percentile widget with product analytics:
- panel toggle
- time-range change
- resource-attributes selector toggle
- attribute selection change.
Events go through the existing useTraceDetailLogEvent hook so view and
traceId are injected.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5908
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
This follows the same pattern as
https://github.com/SigNoz/signoz/pull/11681 to use `latest` instead of
`avg`, and also fixes the calculation of `util %` that was suppose to be
`desired/available * 100` instead of current value `available/desired`.
#### Screenshots / Screen Recordings (if applicable)
> Include screenshots or screen recordings that clearly show the
behavior before the change and the result after the change. This helps
reviewers quickly understand the impact and verify the update.
Before:
<img width="857" height="364" alt="image"
src="https://github.com/user-attachments/assets/949db1a8-c27d-41da-8573-398a4d53af24"
/>
After:
<img width="851" height="336" alt="image"
src="https://github.com/user-attachments/assets/5181849a-28e4-45f8-b7a5-0d611b5ae02e"
/>
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic
closure on merge.
Closes https://github.com/SigNoz/pulse-pod/issues/210
---
### ✅ Change Type
_Select all that apply_
- [ ] ✨ Feature
- [x] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated: No
- Manual verification: Yes
- Edge cases covered: -
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: Infrastructure Monitoring - Namespaces
- Potential regressions: None
- Rollback plan: Revert this commit
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Bug Fix |
| Description | We updated the table for Desired (pods) inside the
Namespace Details on Infrastructure Monitoring to correctly show the
`util %`. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
#### Description
- Some data sources ship a single doc that sets up two or three signals,
but carried only one tag, so they showed up in exactly one section of
the picker. Searching `temporal` surfaced it only under APM/Traces even
though both Temporal docs configure traces, metrics and logs.
- Tagged them with every signal their doc actually configures, so they
list under each matching section — the same way `Deno` already does. No
UI changes needed: `groupDataSourcesByTags` already fans an entry out
across its tags.
| entry | was | now |
| --- | --- | --- |
| Temporal | `apm/traces` | `apm/traces`, `logs`, `metrics` |
| Nginx - OpenTelemetry (was "Nginx - Tracing") | `apm/traces` |
`apm/traces`, `logs`, `metrics` |
| OpenTelemetry eBPF (OBI) | `apm/traces` | `apm/traces`, `metrics` |
| DBOS | `apm/traces` | `apm/traces`, `logs` |
| Cloudflare Workers | `apm/traces` | `apm/traces`, `logs` |
- "Nginx - Tracing" is renamed to "Nginx - OpenTelemetry" since it no
longer lists only under traces, and to stay distinct from the existing
built-in Nginx integration entry.
#### Additional Information
- All 82 docs behind the 70 single-signal-tagged entries were read to
decide this; the other 77 are genuinely single-signal. Every language
APM doc explicitly sets `OTEL_METRICS_EXPORTER=none` /
`OTEL_LOGS_EXPORTER=none`, and the matching metrics docs set
`OTEL_TRACES_EXPORTER=none` — so splits like `Java` / `Java logs` /
`Java Metrics` are correct as they stand.
- Left unchanged, but worth a second opinion: the logs docs for Java,
Python, Node.js (Pino/Winston/Bunyan) and Golang (Logrus/Zerolog) run
auto-instrumentation that emits traces, but only ever mention traces to
tell you how to switch them off. Read as logs-only here.
### Description
Bumps `github.com/AfterShip/clickhouse-sql-parser` from v0.5.5 to
v0.5.6.
- v0.5.6 parses a parenthesized left operand of a set operator (upstream
https://github.com/AfterShip/clickhouse-sql-parser/pull/312), e.g.
`(SELECT 1) UNION ALL (SELECT 2)`.
- Moves the three now-passing parenthesized set-operation cases into the
pass table in `clickhouse_sql_test.go` as regression canaries.
- Records the outstanding `NULLS FIRST|LAST` ORDER BY gap in the
known-gap table — the parser still rejects it, so it stays tracked until
fixed upstream.
#### Description
- `IN` and `NOT IN` now route each value back through the condition
builder with `=` / `!=` instead of assembling the comparisons a second
time, so whatever a builder does for a scalar comparison applies to the
list form too. Applied to logs, traces and audit — the three that
already fanned a list out into per-value comparisons.
- Fixes `body.<path>[*] IN [...]` with `use_json_body` off returning a
**500**. The list shape made the path extract as `Array(String)`, and
ClickHouse refuses to compare an array to a scalar (code 130);
extracting per value reads the field instead. The new case in
`querierlogs/06_json_body.py` fails on `main` and passes here — verified
both ways against a real ClickHouse.
#### Additional Information
- **resourcefilter is deliberately left out**: it asserts the key index
filter (`labels LIKE '%key%'`) once for the whole list, alongside one
value filter per value. A recursed arm derives its own key filter, so
the same predicate would be repeated per value — `(e1 AND kIdx AND l1)
OR (e2 AND kIdx AND l2)` instead of `(e1 OR e2) AND kIdx AND (l1 OR
l2)`. Same rows either way, but no reason to emit the duplicate.
- **telemetrymetadata is deliberately left out**: it applies a
key-existence guard at a single exit, so a recursed arm comes back
already wrapped and the guard would either nest or the case would have
to skip the shared tail — losing the invariant that every condition is
guarded.
- **metrics and rulestatehistory** build a real `sb.In`; there is no
per-value fan-out to delegate to.
- Mixed-type lists are safe: `DataTypeCollisionHandledFieldName`
normalises the whole list before the loop, so `IN ('200', 5)` emits
identical SQL before and after.
- Base of a stack — the follow-ups add index-friendly predicates to `=`,
which `IN` then picks up.
#### Description
Stacked on SigNoz/signoz#12491 — review that one first.
#12491 makes the frontend parser lex and parse `search()`. This makes
the editor act on it. Scoped to `search('x')` and `search(x)` for now —
scope arguments are not handled yet.
- **Function suggestion.** The cursor on `search` resolved to no
context, so the suggestion list never opened and `search()` was never
offered as a completion. Registered alongside the `has` family, in the
same two places `hasToken` needed.
- **The term is free text, not a key.** `search(x)` lexes its term as a
key, so the editor offered attribute keys inside the call and would
complete one into the term — and pair extraction turned it into a filter
item keyed `x`, which the log detail drawer rebuilds into a real filter.
Key and value suggestions are now suppressed inside a `search()` call,
and its argument no longer produces a pair. `has(key, value)` does take
a real key, so its suggestions are untouched.
- **Logs only.** `FilterOperatorSearch` is implemented in
`logstelemetryschema` alone; traces and metrics reject it as an
unsupported operator, so the suggestion is gated to the logs signal.
- **Recents.** `SEARCH(...)` and `search(...)` no longer dedup as two
distinct recent queries.
#### Additional Information
`search` is a reserved word now, so `search = 'x'` and `search exists`
no longer parse — the same tradeoff `has`/`hasAny` already carry,
matching the backend grammar.
Three things deliberately left out:
- After picking any function from the autocomplete the cursor lands
outside the brackets, so you have to arrow back before typing the
argument. Long-standing behaviour across the whole `has` family, but
`search()` is the case where an empty call is always a syntax error.
Filed as SigNoz/engineering-pod#5893.
- `has(key, value)` still contributes a phantom filter item keyed on its
first argument, which reaches the trace waterfall's API query, the
metrics drilldown and the infra filter telemetry. Fixing it needs the
autocomplete to keep reading that argument as a key, so it is not a
one-liner.
- Scope arguments (`search('err', body)`) parse but are not supported
here.
`isCursorInSearchTerm` runs on every cursor move, so it text-matches
`search` before paying for a lex. A `SEARCH` token only exists where the
lexer matched exactly those six letters — a word character on either
side would have produced a `KEY` — so the pre-check cannot produce a
false negative. `body = 'search this'` is covered by a test, since only
the lexer can tell that one is a quoted value.
Unrelated to this PR: `QuerySearch.test.tsx › fetches key suggestions on
mount for LOGS` is flaky on `main` too. An earlier test in that file
types `http.` and never unmounts, so its debounced `getKeySuggestions`
resolves after this test's `mockClear()` and wins the `mock.calls[length
- 1]` read.
#### Description
- The integration suite was the last consumer of the five deprecated v1
user endpoints. It now provisions through `POST /api/v2/users`, `PUT
/api/v2/users/{id}/reset_password_tokens` and `POST
/api/v2/factor_password/reset`, so those routes can be deleted once the
remaining upstream consumer is deployed.
- Role assignment moves off the deprecated `POST
/api/v2/users/{id}/roles` and `DELETE /api/v2/users/{id}/roles/{roleId}`
onto `/api/v2/user_roles`. Removal is keyed by the `user_role` entry id,
so the tests read it from `GET /api/v2/users/{id}`. `GET
/api/v2/users/{id}/roles` is not deprecated and stays.
- `create_active_user` takes managed role names (`signoz-viewer`),
matching `change_user_role` and `create_service_account`.
- `find_role_by_name` moves to `fixtures/role.py` as a plain function
and replaces the `find_role_id` fixture — a stateless lookup shouldn't
be a fixture factory.
#### Issues closed by this PR
Contributes to SigNoz/platform-pod#2667
#### Additional Information
- `test_provision_user` now makes the provisioning calls inline, in
order, and covers the conflict branch that had no coverage before.
- `test_reset_password_v2` is gone; `test_reset_password` now targets v2
and absorbed its single-use-token assertion.
#### Description
- The frontend parser under `frontend/src/parser/` is generated from
`grammar/FilterQuery.g4` — the same grammar the backend query builder
uses — but the committed output predates the `search()` rule, so the UI
couldn't lex or parse `search('term')` even though the backend accepts
it. Regenerated it.
- Also fixed `scripts/grammar/generate-frontend-parser.sh`: ANTLR
reproduces the input's relative path under `-o`, so the old command
wrote to `frontend/src/parser/grammar/` instead of
`frontend/src/parser/`. It now runs from inside `grammar/`.
- Generated with ANTLR 4.13.2 (was 4.13.1), matching the `antlr4`
runtime in `frontend/package.json` and the version used for the Go
parser. That bump also adds `.js` extensions to the generated relative
imports — tsc (`moduleResolution: bundler`) and ts-jest resolve them
fine.
- Codegen only: no visitor/UI wiring for `search()` yet, so surfacing it
in autocomplete/validation is follow-up work.
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5875
#### Additional Information
- Verified with a throwaway spec (not committed) that the regenerated
parser parses `search('error')`, `search('error', body, attribute)` and
`search('error') AND service.name = 'redis'` with zero syntax errors,
and still parses `has(payload.user_ids, 123)`.
- `src/parser/**` is in both the oxfmt and oxlint ignore lists, so the
raw ANTLR output is committed unformatted, as before. Because every
staged frontend file is ignored, lint-staged's `oxfmt --write` step
errors with "Expected at least one target file" — the commit needed
`--no-verify`.
- Ran the jest suites that consume the parser (`src/utils`,
`src/components/QueryBuilderV2`, `src/lib/recentQueries`): 371 passed, 1
pre-existing failure in `QuerySearch.test.tsx` that reproduces on `main`
without these changes.
<!--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
In the logs explorer **table view**, a column for a field that lives
inside a JSON body now shows its value instead of coming up empty.
Scoped to `use_json_body` tenants.
- Added a wrapper util which sits on top of existing util which provides
the col values(FlatLogData).
- This searches for the attribute in body json. If its not present there
we will get it from attribute/resources as its happening currently.
- Only does this if `use_json_body` is true.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/4610
<!--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:
DB Operation col is empty
<img width="3402" height="1850" alt="image"
src="https://github.com/user-attachments/assets/7f8c6945-2c43-4ea6-9b82-5fd07b36c52f"
/>
After:
DB Operation col is populated from body
<img width="3346" height="1778" alt="image"
src="https://github.com/user-attachments/assets/c1ae2953-986e-42d5-bef3-9bc10e932fc2"
/>
#### Description
- For orgs on JSON bodies the collector writes the legacy `body` column
empty (`processBody` blanks it unless `body_json_old_body_enabled`) and
keeps the log body in `body_v2`. The v3 logs list still selected `body`,
so every log came back with an empty body — verified on a tenant: all
2159 rows had `body = ''` and `body_v2` populated.
- `queryRangeV3` now resolves `use_json_body` for the caller's org and
the list query selects `body_v2 as body`, the same expression v5 uses.
- `GetListResultV3` decodes JSON columns into a map, mirroring the
querier's raw-row consumption — the driver cannot decode JSON into
native Go values, so it is read as raw bytes and unmarshalled. A v3
response now carries the same body object v5 returns, so clients need no
change when they move to v5.
#### Additional Information
Scoped to the v3 endpoint: `QueryRangeV4` does not set the flag, so v4
keeps selecting the legacy column even though it shares the builder. The
livetail select is untouched — `/api/v3/logs/livetail` is served by the
v5 handler now, and `PrepareLiveTailQuery` has no callers.
One difference from v5 remains by choice: v5's `postProcessLogBody`
drops an empty `message` key, so a log with an empty message reads
`{"message":""}` here and `{}` there. Nothing branches on it, and
copying that logic would be a second implementation to keep in sync on a
path we are retiring.
#12520 is stacked on this branch — it types the pipelines preview log
body as `any`, which that endpoint needs before it starts receiving the
object bodies this PR returns.
<!--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
- Adding GCP integration MySQL service
- Related fix: adding formula to convert CPU utilization fraction into
percentage for Postgres dashboard
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
https://github.com/SigNoz/platform-pod/issues/2942
<!--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
Filter field selector options with name field empty
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
closes https://github.com/SigNoz/engineering-pod/issues/5890
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
No screen recording as this is hard to reproduce.
## Pull Request
---
### 📄 Summary
Currently, builder and clickhouse queries remove NaN and Inf values, but
PromQL does not. This way, it ends up in the final response. While the
UI handles these values, a lot of other places in the flow do not, such
as our query response caching. This can lead to unexpected issues.
The current issue at hand is that while the first query range call shows
the correct data, the second call (that fetches from cache) does not.
Instead of fixing the caching, better to solve the problem at root level
and not return non-finite values for PromQL altogether.
#### Recordings
On local data before the change:
https://github.com/user-attachments/assets/c08ec796-a7e5-47d8-8cc5-3dfd302dba49
After the change:
https://github.com/user-attachments/assets/9162c963-1a98-4ebc-83ce-759a9127b772
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/185
---
### 🧪 Testing Strategy
- Tests added/updated: Yes, integration and unit tests
- Manual verification: Added data locally to reproduce the exact
scenario
---
### ⚠️ Risk & Impact Assessment
- Blast radius: PromQL queries
- Rollback plan: Revert PR or just add a fix
---
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
This PR drops Cancelation error from monaco on sentry to reduce noise
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
Pager: https://signoz-1.pagerduty.com/incidents/Q1JG9MJ5DRA4LW
Sentry: https://signoz-io.sentry.io/issues/7491905006
#### Description
Selecting a filter on the Service Map cleared the filter bar instead of
applying it, and the same filter then turned up applied on the Services
tab. Three separate causes:
- The resource attribute context filtered its queries by the current
route, so a filter the map cannot apply vanished from the bar while
staying in state and in the `resourceAttribute` URL param — which the
sidebar carries across routes, hence it reappearing on Services. The
context now exposes whatever is in the URL, and the Service Map narrows
the queries for its own `/dependency_graph` request, so the request
payload is unchanged.
- `ServiceMap` returned early with the filter bar under a different
parent element in each branch, so React tore the bar down and rebuilt it
whenever the map flipped between having services and being empty (and it
wasn't rendered at all while loading). It now renders once, above the
loading / empty / map states. As a side effect the graph tooltip styles
in `Container` finally wrap the graph rather than only the empty state.
- The environment `Select` was keyed on its own value, remounting an
already-controlled select on every pick and closing the dropdown before
a second environment could be chosen.
#### Issues closed by this PR
ClosesSigNoz/pulse-pod#199
#### Additional Information
- Related but deliberately left out of scope: `whilelistedKeys` lists
`resource_k8s_cluster_namespace`, while the backend column is
`k8s_namespace_name` (`pkg/query-service/app/services/map.go`), so that
filter is accepted by the UI and silently dropped server side.
## Summary
Opening an existing alert rule for editing crashes the whole page with
`Cannot read properties of null (reading 'length')`. It happens when a
rule's threshold has `channels: null`, which is the case for rules not
created through the UI.
The generated type is right — `RuletypesBasicRuleThresholdDTO.channels`
is `string[] | null`. The problem is our own `BasicThreshold` wrapper
type, which we keep because v1 and v2 alert shapes both exist. It says
`channels: string[]`, and `fromRuleDTOToPostableRuleV2` casts the DTO
straight into it with `as unknown as`. So the null reaches our code
while the compiler thinks it can't.
`getThresholdStateFromAlertDef` copied that null into state, and the
footer validator then read `.length` on it during render, which takes
down the page instead of failing one field.
This PR defaults `channels` to `[]` where the API data becomes local
state, so the validator, the payload builder and both channel dropdowns
are all safe. The validator also gets an optional chain, since a throw
there can't be recovered.
This is a guard, not the real fix. The cast in
`fromRuleDTOToPostableRuleV2` is the actual gap, and the same wrapper
also claims `spec` is non-nullable when the generated type allows null —
so `spec.map` and `spec[0].op` in the same function can still crash.
Worth fixing at the converter.
## Test plan
One test per guard. Both fail with the original error when the fix is
reverted.
- `pnpm jest src/container/CreateAlertV2/` — 420 pass, 28 suites
- `oxfmt`, `oxlint`, `tsgo --noEmit` clean
Closes https://github.com/SigNoz/pulse-pod/issues/261
## Summary
The Google Chat save test fails on CI now and then with `Exceeded
timeout of 5000 ms for a test`.
The two Google Chat tests fill the form with `userEvent.type()`, which
sends one keystroke at a time. Each keystroke re-renders the whole form.
The payload test types 91 characters, so it takes ~850ms locally. CI is
about 5x slower, which puts it near the 5s limit. A busy runner then
pushes it over.
This PR pastes the values instead of typing them. One event per field,
same assertions.
| Test | Before | After |
| --- | --- | --- |
| `saving sends a googlechat_configs payload` | 847 ms | 283 ms |
| `saving with a webhook url outside chat.googleapis.com` | 590 ms | 326
ms |
Nothing regressed. The new test was added recently to an already
existing suite. It was always close to the limit.
## Test plan
- `pnpm jest
src/container/AllAlertChannels/__tests__/CreateAlertChannel.test.tsx` —
57/57 pass, 3 runs
- `oxfmt`, `oxlint`, `tsgo --noEmit` clean
Closes https://github.com/SigNoz/pulse-pod/issues/259
#### Description
- `sendInvite` has had no call sites since the invite flow moved to
`POST /api/v2/users`. Removing it orphans its types file, the two MSW
handlers for `/api/v1/invite` and `/api/v1/user`, and the members mock
data they served.
- No product behaviour changes; nothing in the app or the tests
requested either endpoint.
#### Issues closed by this PR
Contributes to SigNoz/platform-pod#2667
we no longer need it, helps with the upcoming sem conv change
- the `transition.go` will go away completely when the sem conv support
for metrics added
- we will add deprecation notice and migration guide for infra
monitoring v1 apis just in case if anyone using it and then remove it
altogether
areas touched and tested
- services
- logs detailed page node/pod metrics for a log
- message queues
## Summary
- Handle malformed selectedFields in the extradata in the migration and
new migration to fix in the already migrated cases.
- Restructure saved-view create/update/get payloads so
`schemaVersion`/`spec` are top-level (unwrapping the old `data`
nesting), matching how dashboards and rules shape their wire types.
- Publish `schemaVersion` as an `enum: [v2]`
- Make `display` and `selectedFields` optional in the OpenAPI schema
- Declare `409` on `CreateSavedView`
- Require `minItems: 1` on `queries`
New API contract in [below
comment](https://github.com/SigNoz/signoz/pull/12477#issuecomment-5230041074),
follow up on https://github.com/SigNoz/signoz/pull/12342
Closes https://github.com/SigNoz/engineering-pod/issues/4651
Notes to reviewer:
- Please pay attention to the last case in above linked comment for
partial display field updates.
- Still assuming that [migration
046](6372af75a6/pkg/sqlmigration/046_update_dashboard_alert_and_saved_view_v5.go (L233))
has already migrated all the views to v5 QB format and don't need to do
that now.
- Breaking change: queries are not validated in the v1 APIs as well, so
any incorrect query will be rejected
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
## Summary
On a V2 dashboard, picking values in a multi-select variable and then
changing the time range could silently switch the variable to **ALL** —
widening every panel to all values without the user touching the
variable. A value *typed into* a variable was dropped on any refetch for
the same underlying reason.
The post-fetch reconcile compares a selection against freshly-fetched
options and could not tell *why* those options changed: "the user has
nothing selected yet" and "the user's selection was just invalidated by
a refetch" arrived as the same input, and both resolved to the
variable's default — ALL for an ALL-enabled multi-select. A time-range
change refetches every variable, so any window without data for the
selected value hit that path.
Two guarantees now, each with its own mechanism:
| Guarantee | Mechanism |
| --- | --- |
| A refetch nothing else caused (time range, reload) never re-defaults a
selection | The fetch engine tags each cycle with why it was enqueued;
only a value cascade may re-default |
| A typed-in value survives every refetch, whatever caused it | The
selection records which entries were typed, judged at pick time against
the options then offered |
A parent variable's value changing still re-scopes its children — that
behaviour is unchanged and intended. Single-select variables have
preserved a non-empty value since #12178; this brings multi-select in
line, which is the half that was left untouched then.
Also fixed, same family: opening and closing the dropdown without
touching it promoted an explicit pick into a standing ALL whenever the
current window offered only the selected values, and rewrote a dynamic
ALL's `__all__` into concrete values.
Closes https://github.com/SigNoz/pulse-pod/issues/207
## Commits
1. `refactor` — record why each variable fetch cycle was enqueued (full
cycle vs value cascade)
2. `fix` — keep a variable's selection across a time-range refetch
3. `fix` — keep typed-in variable values through every refetch; ALL now
means exactly the option set
4. `fix` — a no-op close of the variable list commits nothing; commit
rule extracted out of the component
Each commit typechecks on its own.
## Test plan
- [x] `jest src/pages/DashboardPageV2` — 136 suites / 1073 tests pass,
including 20 added: the reconcile split by cycle reason, the cycle
tagging in the store, a time-range change tagging every variable as a
full cycle, the typed-value rules, and the commit resolver
- [x] `tsgo --noEmit` clean, at every commit
- [x] `oxlint` and `oxfmt --check` clean on the changed files
- [x] Manual: multi-select variable, pick one value, switch to a window
with no data for it → selection holds, panels show no data rather than
everything
- [x] Manual: type a custom value into a variable, change the time range
and switch a sibling variable → the typed value stays selected
- [x] Manual: namespace → pod pair, change namespace → pod values still
re-scope
## Notes for reviewers
- `customValues` is new on the runtime selection and is persisted with
it. It never reaches the wire or a shared link: `buildVariablesPayload`
and the share-URL builder both project `value` / the `__all__` sentinel
explicitly.
- A selection seeded from a `?variables=` share link carries no
typed-value marker — that URL format stores `name → value` only, so a
typed value from a link is indistinguishable from a fetched one and a
cascade can still drop it.
- The pill can still *read* ALL when the current option list happens to
be a subset of the selection: `CustomMultiSelect` infers that from
`options ⊆ value`, and V1 depends on the inference. It is display-only
now and self-corrects as the window widens; making it exact needs an
explicit prop on the shared control.
#### Description
If a dashboard failed to migrate to the new schema, currently the delete
API does not delete them. This PR changes it to be able to delete those
un-migrated dashboards as well.
#### Issues closed by this PR
Closes https://github.com/SigNoz/signoz/issues/12390
<!--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
Added missing tests for covering GCP integrations API, which initially
was just covering AWS
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
https://github.com/SigNoz/platform-pod/issues/2899
#### Description
- Removes `test_filter_expressions_no_server_error`, which fires all
9,999 lines of `filter_expressions_10000.txt` at the logs endpoint, one
request at a time. It dominates a local integration run, enough that the
habit is to comment it out first.
- It asserted almost nothing: both `200` and `400` pass, so all it
checked was that the server didn't crash.
- Its corpus has no `[*]` array paths, so it missed `body.<path>[*] IN
[...]` returning a 500 — exactly the failure it exists to catch. That
one is fixed in #12504, with a targeted case that asserts the rows
returned.
- Deletes the corpus file too; nothing else reads it.
`test_not_filter_expression` is untouched and its 11 cases still pass.
#### Additional Information
This does give up cheap crash-fuzzing breadth in CI, where runtime
matters less than it does locally. If that breadth is worth keeping, the
alternative is a much smaller curated corpus that includes the shapes
this one misses — happy to do that instead.
## Pull Request
---
### 📄 Summary
The "Recent searches" dropdown (introduced in #11523) is built into the
`QuerySearch` editor and reads from per-signal localStorage buckets, but
entries were only ever **saved** by the QueryBuilder provider's
`handleRunQuery` — a path only the Logs/Traces/Metrics explorers go
through. Every other page embedding `QuerySearch` runs queries through
local handlers, so searches run there displayed explorer recents but
were never captured themselves.
This PR closes that gap for:
| Route | Surface | Save triggers |
|---|---|---|
| `/metrics-explorer/summary` | Metrics Summary search bar | Run button,
Cmd+Enter |
| `/infrastructure-monitoring/hosts` | Host details drawer → Logs /
Traces tabs | Run button, Cmd+Enter |
| `/infrastructure-monitoring/kubernetes` | Entity details drawer → Logs
/ Traces / Events tabs | Run button, Cmd+Enter |
**Approach:** rather than fabricating a fake composite query on each
page, a new expression-level helper
`saveRecentQueryByExpression(dataSource, expression, source?)` is added
to `lib/recentQueries`. It owns the shared policy (trim →
`validateQuery` → signal check → save), and the existing composite-level
`saveRecentQuery` now delegates to it, so validation rules live in
exactly one place. Saves land in the same per-signal buckets the
dropdown already reads, so recents are shared with the explorers both
ways.
Deliberate choices:
- **Infra entity tabs** save the user-typed expression only (not the
combined entity-scoped one), matching what the recents dropdown inserts
back into the editor.
- Expressions are now stored trimmed; dedup was already
trim-insensitive, so this only cleans up display labels.
#### Screenshots / Screen Recordings (if applicable)
https://github.com/user-attachments/assets/852c6273-c797-4281-ac8c-be57eedf5d77
#### Issues closed by this PR
Closes
https://github.com/orgs/SigNoz/projects/39/views/11?filterQuery=assignee%3Atewarig&pane=issue&itemId=210386109&issue=SigNoz%7Cengineering-pod%7C5650
---
### ✅ Change Type
_Select all that apply_
- [ ] ✨ Feature
- [x] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🐛 Bug Context
---
### 🧪 Testing Strategy
- Tests: existing `lib/recentQueries` unit tests still pass (37/37) —
`saveRecentQuery` now routes through the new helper, so its behaviour
stays covered.
- Manual verification: `tsgo --noEmit`, `oxlint` (no new warnings), and
production build all pass.
- Edge cases covered: invalid/partial expressions are rejected by
`validateQuery` before saving; empty/whitespace-only expressions and
unsupported data sources are no-ops.
---
### ⚠️ Risk & Impact Assessment
---
### 📝 Changelog
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Bug Fix |
| Description | Searches run on Metrics Summary and Infra Monitoring
entity detail tabs (Logs/Traces/Events) now appear in the "Recent
searches" dropdown, shared with the explorers. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [x] Breaking changes documented
- [x] Backward compatibility considered
---
## 👀 Notes for Reviewers
---
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- Member role assignment used the deprecated `POST` / `DELETE
/api/v2/users/{id}/roles`. It now uses `POST` and `DELETE
/api/v2/user_roles`, which had no consumers until now.
- Roles are read from `useGetUser` instead of `useGetRolesByUserID`,
because the delete route is keyed by the `user_role` join row and only
that response carries its id.
#### Issues closed by this PR
ClosesSigNoz/platform-pod#2918
#### Screenshots / Screen Recordings
https://github.com/user-attachments/assets/4c0790fb-a93e-4c37-8608-51fe03d4d962
#### Additional Information
- `EditMemberDrawer` already issues the same `useGetUser` query, so the
two share one request and the component itself needed no change.
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
This PR filters network request aborts via fetch and axios in beforeSend
so they stop surfacing as Sentry issues.
- axios: ECONNABORTED ("Request aborted"), ERR_CANCELED
- native fetch: AbortError
<!--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
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
<!--Please delete paragraphs that you did not use before submitting.-->
#### Description
- The reset password page still called the deprecated `POST
/api/v1/resetPassword`. It now uses the generated `useResetPassword`
hook, which targets `POST /api/v2/factor_password/reset`.
- Deletes the hand-written v1 client and its types; nothing else
referenced them.
#### Issues closed by this PR
Contributes to SigNoz/platform-pod#2667
#### Screenshots / Screen Recordings
https://github.com/user-attachments/assets/9804f619-5928-4c72-83a2-2aae16855e7f
#### Additional Information
- Manual `loading` and `errorMessage` state give way to the hook's
`isLoading` and `convertToApiError`, matching how `ForgotPassword`
consumes its generated hook.
- Once this merges, `POST /api/v1/resetPassword` has no callers left in
the product.
Setting the query expression to a value containing CRLF line breaks
crashed the search bar with "RangeError: Selection points outside of
document".
CodeMirror normalises CRLF to LF when building a change, so the
resulting document is shorter than the raw string. The selection anchor
used value.length (pre-normalisation), which pointed past the end of the
document.
Build the ChangeSet first and anchor the selection at changes.newLength,
the actual post-change document length. Adds a regression test.
<!--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
To fix the above mentioned problem, we now switch the cursor position
from `value.length` (which is not yet normalized by CodeMirror) to
`changes.newLength`, which is the normalized length.
Added test case
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/engineering-pod/issues/5869
<!--If applicable, include screenshots or screen recordings that clearly
show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
Before
https://github.com/user-attachments/assets/ec7a3182-177f-4545-9bae-83ee0c3a61db
After
https://github.com/user-attachments/assets/06350698-1960-47c2-b65e-81ea2d10b15d
Possible options
1. The compatibility keys maps (the approach already in the code).
`backward_compat_keys.go` makes an alias key at metadata time. We
rejected this option because of evidence. The alias key resolves, but it
reads the wrong data. It prepares to `attributes_string['<alias>']`, and
that physical key does not hold the data.
2. The flat multi-key.
`GetKeys` returns multiple keys in order, and the downstream code uses
the list. The option fails on semantics. It removes one piece of
information that the downstream must have. The downstream must know the
difference between two cases:
- Two keys are the same field with two spellings so we can merge them
into one expression.
- Two keys are different fields with the same name. The condition
builder must make one condition for each key. The operator connects the
conditions.
Three failures show the problem:
- Negative operators connect with OR across the keys. A row that has
only one spelling then always matches. Example: `env != 'prod'` matches
each row that does not have one of the two keys.
- A row that has both spellings with different values gets no clear
result.
- A value position (group-by, select) needs exactly one expression for
one field. A flat list cannot point to that expression.
The information must live somewhere.
3. Annotations on `TelemetryFieldKey`
Maintain the `SemconvMembers` and `SemconvMaterializedColumns` fields on
the keys. The information is the same as in option 4. But it's awkward
because "these N keys are one family, in this order" lives in N copies,
one copy on each key.
4. introduce `LogicalField`
The information is the same as in option 3, but the structure holds it:
- The slice is the ambiguity.
- The group is the family.
- The member order is the precedence. The code sorts the members one
time, by family rank, at construction.
- The members point to the metadata entries. The code copies nothing and
changes nothing.
- The identity (signal, context, data type) is on the group. A merge
across contexts or data types is not possible. The design does not avoid
that merge; the design cannot express it.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
**What it does:** Slice 2 of the log-details drawer revamp, a new
**Highlights** row at the
top of the drawer that surfaces a log's key fields as chips. Gated
behind `isLogDetailsV2`
(ships off). Stacked on the header PR (#12310 /
`feat/log-detail-revamp`); the DataViewer
lands in the next PR.
**Change points**
- New Highlight section added. Check screenshot
- Driven by config.
- Severity chip color
- Trace id click opens trace details page in new tab
- Tests updated
#### Screenshots / Screen Recordings (if applicable)
<img width="2158" height="834" alt="image"
src="https://github.com/user-attachments/assets/9b9af5b0-8437-4a6d-8b34-e97b0d55de26"
/>
<img width="2308" height="920" alt="image"
src="https://github.com/user-attachments/assets/69e3702f-cc25-4570-87d2-67a000925705"
/>
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic
closure on merge.
---
### ✅ Change Type
_Select all that apply_
- [x] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🐛 Bug Context
> Required if this PR fixes a bug
#### Root Cause
> What caused the issue?
> Regression, faulty assumption, edge case, refactor, etc.
#### Fix Strategy
> How does this PR address the root cause?
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated:
- Manual verification:
- Edge cases covered:
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius:
- Potential regressions:
- Rollback plan:
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Feature / Bug Fix / Maintenance |
| Description | User-facing summary |
---
### 📋 Checklist
- [ ] Tests added or explicitly not required
- [ ] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
---
## 👀 Notes for Reviewers
<!-- Anything reviewers should keep in mind while reviewing -->
---
#### Description
- Documents the kind/spec envelope pattern for sum types in
`docs/contributing/go/types.md`: the envelope shape, why it goes at the
point of variance rather than the resource root, the tagging-style
rationale (adjacently tagged vs internally tagged vs sibling optional
fields), the validating `UnmarshalJSON`, the OpenAPI variant structs,
and the data-migration-vs-storable-twin trade-off for legacy persisted
shapes.
- Examples are generic (`FooConfig` with `bar`/`baz` kinds), with
`RuleThresholdData`, `EvaluationEnvelope` and the dashboard plugins as
the in-tree references.
- Cross-links from `handler.md`'s "`oneOf` with a discriminator"
section, which keeps owning the schema mechanics.
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
Adds **Google Cloud Platform** as a cloud integration, bringing GCP to
parity with the existing AWS and Azure integrations. Users can now
connect a GCP account and manage which projects are monitored, directly
from the Integrations UI.
The integration reuses the shared cloud-integration surfaces (services
list, service details, account actions, header) and adds the
GCP-specific data-entry flows on top
doc -
https://app.notion.com/p/signoz/GCP-Integration-frontend-requirements-39cfcc6bcd1980b39428e1e66a77893b#39cfcc6bcd19808aa9bccef55661bf9d
Artifacts-
https://claude.ai/design/p/3591c3c0-3cc5-42ca-9ff9-dc8698e4d4dc?file=GCP+Integration.dc.html
The commits are stacked so that **each one type-checks, builds, and runs
on its own** — you can check out any commit and use the GCP page in the
state that commit describes:
1. c5f550d88 — **GCP page, runnable end-to-end minus the drawers**: type
definitions, constants, DTO→UI account mapping, logo, and the wiring
into the shared integration UI (`IntegrationDetailPage` routes the `gcp`
id to `CloudIntegration`, `AccountActions` maps GCP accounts,
`ServiceDetails` handles the GCP service config shape, remove-account
copy). Also narrows the Azure config checks to `resource_groups`, since
the widened config union means `deployment_region` no longer uniquely
identifies Azure. At this commit the GCP page renders and lists
accounts/services; the add-/edit-account buttons are intentionally
no-ops.
2. dc4b4c3d0 — **Add-account drawer**: the cloud account setup drawer
(flow selector, connection secret fields, field validators, and the
`useCloudAccountSetupDrawer` orchestration hook), wired into
`AccountActions`. Secret fields use the shared periscope `CopyButton`,
which gains an optional `onCopy` callback for the toast.
3. 4f45b3a69 — **Edit-account drawer**: the account settings drawer for
an already-connected GCP account, wired into `AccountActions`.
4. 4f3fe7ec6 — **Fix connection-status polling**:
`isOneClickIntegration` only listed AWS and Azure, so
`IntegrationDetailPage` treated GCP as a legacy integration and polled
`GET /integrations/gcp/connection_status` every 5s via
`useGetIntegrationStatus`. GCP has no such legacy status endpoint, so
it's added to the allowlist to disable the poll — bringing it to parity
with AWS/Azure.
#### 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/7bd5f9d3-85ff-452d-85f4-47eb95c0cce6
#### Issues closed by this PR
https://github.com/orgs/SigNoz/projects/39/views/11?filterQuery=assignee%3Atewarig&pane=issue&itemId=212300300&issue=SigNoz%7Cengineering-pod%7C5684
N/A
---
### ✅ Change Type
_Select all that apply_
- [x] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated:
Will add tests in follow up
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius: Scoped to the Cloud Integrations feature. Shared
components (`AccountActions`, `ServiceDetails`,
`RemoveIntegrationAccount`) gained GCP branches guarded by `type ===
GCP_SERVICES`, so AWS/Azure paths are unaffected.
- Potential regressions: The Azure config-narrowing change is the only
edit to existing Azure behavior; verified the Azure edit modal still
type-checks and reads `resource_groups`/`deployment_region` correctly.
- Rollback plan: Revert the PR — GCP is additive and gated by provider
type, so removal leaves AWS/Azure untouched.
---
### 📝 Changelog
| Field | Value |
|------|-------|
| Deployment Type | Cloud |
| Change Type | Feature |
| Description | Added Google Cloud Platform cloud integration: connect a
GCP account and manage monitored projects from the Integrations UI. |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [x] Breaking changes documented (none — additive)
- [x] Backward compatibility considered
---
## 👀 Notes for Reviewers
> The PR is best reviewed commit by commit — each commit is
independently green (tsc, lint, build) and runnable, so you can check
out any of the three and exercise the GCP page at that stage.
---------
Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
#### Description
- Adds end-to-end coverage for the google authn flow
(`callbackauthn/04_google.py`): happy-path login, hd-claim mismatch
rejection, unverified-email rejection + `insecureSkipEmailVerified`
opt-in, and roleMapping defaultRole.
- The google callback authn hardcodes `https://accounts.google.com` as
its issuer and fully verifies the RS256 id_token, so a wiremock
container impersonates Google: it joins the test network under the
`accounts.google.com` alias and serves HTTPS with a certificate issued
by a new integration CA (`tests/fixtures/tls.py`), which every signoz
container now trusts via `SSL_CERT_FILE`.
- Stubs (discovery, auto-approving authorize, token, JWKS) are installed
per test via the existing `make_http_mocks` fixture with a pre-signed
id_token for the identity under test; one session-scoped RSA key signs
all tokens.
#### Description
- `POST /api/v1/resetPassword` was the last password endpoint with no v2
equivalent. Adds `POST /api/v2/factor_password/reset`, next to the
existing `/factor_password/forgot`, so the whole recovery flow lives
under one namespace.
- v1 keeps working and is now marked deprecated. Both routes share the
same handler, so behaviour is identical.
- A malformed request body now returns a structured 400 instead of a
500. This applies to v1 too, since the handler is shared.
#### Issues closed by this PR
Contributes to SigNoz/platform-pod#2667
#### Additional Information
- The generated frontend client is included because CI re-runs `pnpm
generate:api` and fails on drift. The UI still calls v1; moving it to
the new `useResetPassword` hook is a separate PR to keep review
ownership split.
- Not fixed here: a reset doesn't revoke existing sessions, though a
voluntary password change does. Worth its own ticket.
## Pull Request
---
### 📄 Summary
> Why does this change exist?
> What problem does it solve, and why is this the right approach?
Slice 1 of the log-details drawer revamp, a reworked drawer **header**,
gated behind the new `isLogDetailsV2` flag (ships off). Highlights and
the DataViewer land in
the following stacked PRs.
**Change points**
- Added a new **Log Details Header** with:
- A formatted timestamp based on the user's timezone
- A ⋯ menu with **Copy log** and **Copy link to log**
- Up/down arrows to move between logs
- An optional **Open in Explorer** button
- Moved the log navigation logic into a separate `useLogNavigation` hook
- Updated the Log Details drawer:
- Shows the new header when the feature flag is on (otherwise keeps the
old title)
- Replaced the WARN/ERROR divider with `LogStateIndicator`, which shows
colors for all log levels
- Moved the copy actions into the ⋯ menu and removed the old inline copy
button in V2
- Uses the new navigation hook for both keyboard shortcuts and header
arrows
- Updated the copy link handler:
- `onLogCopy` now accepts an optional click event, so it can be called
from the ⋯ menu
- Moved the "Copied to clipboard" toast from the top-right to the
bottom-right
- Everything is gated behind feature flag for now
#### Screenshots / Screen Recordings (if applicable)
https://github.com/user-attachments/assets/4a73e299-4715-4feb-81e3-762fdc3d0757
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic
closure on merge.
---
### ✅ Change Type
_Select all that apply_
- [ ] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🐛 Bug Context
> Required if this PR fixes a bug
#### Root Cause
> What caused the issue?
> Regression, faulty assumption, edge case, refactor, etc.
#### Fix Strategy
> How does this PR address the root cause?
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated:
- Manual verification:
- Edge cases covered:
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius:
- Potential regressions:
- Rollback plan:
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Feature / Bug Fix / Maintenance |
| Description | User-facing summary |
---
### 📋 Checklist
- [ ] Tests added or explicitly not required
- [ ] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
---
## 👀 Notes for Reviewers
<!-- Anything reviewers should keep in mind while reviewing -->
---
#### Description
- Removes the msw mock for `/api/v1/loginPrecheck` — the endpoint no
longer exists in the backend and nothing in the frontend calls it
anymore; the login flow runs on `/api/v2/sessions/context`.
#### Description
- Invited members can now be given more than one role. The picker was
single-select even though `POST /api/v2/users` has accepted a list of
roles since custom roles landed.
- Frontend only — nothing changed on the backend, the grant chain was
already multi-role.
- Onboarding analytics now emits `teamMembers[].roles` as a list,
replacing the singular `role` key.
#### Issues closed by this PR
ClosesSigNoz/platform-pod#2920
#### Screenshots / Screen Recordings
#### Members Page
https://github.com/user-attachments/assets/8b556549-789c-4ddf-b4af-5994eccc75f3
#### Onboarding Flow
https://github.com/user-attachments/assets/a6dde5d3-e9b4-48c1-965f-352bb0a6a89f
#### Additional Information
- A row still requires at least one role to be considered valid.
- Existing invite tests moved to `findByTitle` for role options,
matching how `EditMemberDrawer` already drives the multi-select.
#### Description
- Removes `GET`, `PUT` and `DELETE /api/v1/user/{id}` — all deprecated
and superseded by `/api/v2/users/{id}`, which the frontend already uses.
- Drops the dead code this leaves behind: the `SelfAccess` middleware
and `Claims.IsSelfAccess` (no callers left), the deprecated update
setters, and three `DeprecatedUser` helpers.
- Points the integration tests that deleted users at `DELETE
/api/v2/users/{id}`.
#### Issues closed by this PR
Contributes to SigNoz/platform-pod#2667
#### Additional Information
- Behaviour change: the removed `GET`/`PUT` were `SelfAccess`, the v2
equivalents are `AdminAccess`. Self-serve reads and updates go through
`/api/v2/users/me`, which is what the UI already calls — but worth a
second pair of eyes.
- `DELETE /api/v1/user/{id}` was the most widely reached of the three.
Please confirm nothing external (zeus) still calls it before merging.
- OpenAPI spec and the generated frontend client are regenerated, not
hand-edited.
#### Description
- Removes `POST /api/v1/invite/bulk` — already deprecated, superseded by
`POST /api/v1/invite`, and no callers left.
- `Setter.CreateBulkInvite` stays; `CreateInvite` still delegates to it
for the single-invite case.
#### Issues closed by this PR
Contributes to SigNoz/platform-pod#2667
#### Additional Information
- OpenAPI spec and the generated frontend client are regenerated, not
hand-edited.
- The `integrationci / fmtlint` failure here is not from this PR — `make
py-lint` is broken on `main`. Fixed separately in #12475; this PR needs
that merged (or a rebase on it) to go green.
- First of three PRs splitting a v1 user-API cleanup. The other two also
regenerate the spec and generated client, so whichever merges second
needs the generators re-run.
#### Description
- `make py-lint` is failing on `main` with `F821 Undefined name
_load_pods_metrics` at `inframonitoring/02_pods.py:471`, which blocks
every open PR.
- `test_pods_filter_pagination_and_ordering` calls a helper that no
longer exists. Replaced with
`Metrics.load_from_file(get_testdata_file_path(...))`, matching the two
other tests that seed `pods_phases.jsonl`.
#### Additional Information
- How it broke: #12460 replaced the module-level `_load_pods_metrics`
with a `load_pods_metrics` fixture, then #12462 dropped that fixture in
favour of calling `Metrics.load_from_file` directly. #12278 branched
before either landed and merged after, re-introducing one call to the
long-gone helper. Git merged cleanly because the branches touched
different lines, so nothing flagged it.
- No behaviour change: the broken call passed no `start_time`, so no
placeholder substitution happened; `load_from_file` with
`label_substitutions=None` does the same earliest-to-`base_time` rebase.
The replacement is byte-identical to the two sibling call sites for the
same dataset.
<!--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
Disabling logs support for all GCP integration services.
Reasons:
1. googlecloudpubsubpush receiver needs to reach Alpha stability, thus
not included in contrib build -> can't suggest for logs
2. signoz otel collector includes this receiver in the build but it
needs upgrade to v0.158.0 for a metrics related
[fix](https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/49826)
-> needs more testing hence can't suggest either
Decision was taken to go ahead without logs for now - follow [ticket
here](https://github.com/SigNoz/platform-pod/issues/2901) for details.
<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Contributes to
https://github.com/SigNoz/platform-pod/issues/2901
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
The explanation above should be enough
<!--Please delete paragraphs that you did not use before submitting.-->
## Pull Request
---
### 📄 Summary
Adds a `filterByPodStatus` secondary filter to the v2 infra-monitoring
list APIs (pods, nodes, namespaces, clusters, deployments, statefulsets,
jobs, daemonsets).
Pod status is a derived kubectl-style value (`k8s.pod.phase` + status
reasons, resolved via `argMax`), not a real label, so it can't go
through the normal query-builder filter. This PR resolves the full-scope
status keyset up-front and intersects it with the metadata + ranked
groups, keeping `total` and pagination correct.
- Multi-select: the field is an array, pushed down as `WHERE
lower(display_status) IN (...)` (OR within status, AND with the
attribute filter).
- When the optional status metrics were never ingested, the endpoint
returns a non-blocking warning + empty page instead of silently
filtering everything out.
#### Screenshots / Screen Recordings (if applicable)
N/A — backend + generated FE API types only; the UI is a separate
change.
#### Issues closed by this PR
Part of SigNoz/engineering-pod#5778.
---
### ✅ Change Type
- [x] ✨ Feature
- [ ] 🐛 Bug fix
- [x] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🐛 Bug Context
N/A — not a bug fix.
---
### 🧪 Testing Strategy
- Tests added/updated:
- Unit test for the status push-down (`applyPodStatusFilter`, built with
go-sqlbuilder).
- Integration tests across all 8 entity APIs: list mode, grouped mode,
validation, missing-metric warning, and multi-select union.
- Manual verification: smoke-tested against staging data (single, multi,
and grouped filters).
- Edge cases covered: missing status metric → warning + empty; grouped
mode keeps a group if ≥1 pod matches; multi-select returns the union of
the selected statuses.
---
### ⚠️ Risk & Impact Assessment
- Blast radius: v2 infra-monitoring list endpoints only.
- Potential regressions: none when the filter is unset (empty = off,
fully additive). When set, an extra status query runs; it is gated
behind the filter being present.
- Rollback plan: revert the PR — no schema or data migrations involved.
---
### 📝 Changelog
| Field | Value |
|------|-------|
| Deployment Type | OSS, Cloud, Enterprise |
| Change Type | Feature |
| Description | v2 infra-monitoring lists can now be filtered by pod
status (multi-select). |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [x] Breaking changes documented
- [x] Backward compatibility considered
---
## 👀 Notes for Reviewers
- `filterByPodStatus` is optional and additive — no change to existing
responses when omitted.
- The status keyset is resolved once at full scope, then intersected —
this is what keeps `total`/pagination correct despite status being a
post-aggregation value.
- OpenAPI spec + FE API types are regenerated (scalar → array); no
hand-written FE.
## Pull Request
---
### 📄 Summary
The infra-monitoring v2 clusters/namespaces list APIs 500 with
ClickHouse error 179 (`MULTIPLE_EXPRESSIONS_FOR_ALIAS`) when the request
groups by an attribute that is also a counted resource attribute (e.g.
clusters grouped by `k8s.node.name` or `k8s.namespace.name`, namespaces
grouped by `k8s.deployment.name`).
Root cause: `getPerGroupDistinctCounts` aliases each `uniqExactIf(...)`
count column with the bare attr key, which collides with the groupBy
column alias for the same key. Fix: alias count columns as
`__count_<attr>`. Row scanning is positional and the result map is keyed
in Go from `attrNames`, so nothing downstream changes.
Integration tests: clusters API grouped by `k8s.namespace.name` and
namespaces API grouped by `k8s.deployment.name`, with exact per-group
`counts` assertions (identity-tuple semantics). Both reproduce the 500
on the pre-fix build and pass on the fixed build.
#### Issues closed by this PR
Closes https://github.com/SigNoz/pulse-pod/issues/245
### ✅ Change Type
_Select all that apply_
- [ ] ✨ Feature
- [x] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🧪 Testing Strategy
- Tests added/updated: Yes — integration (`04_namespaces.py`,
`05_clusters.py`): new groupBy-on-counted-attr cases + per-group
`counts` assertions on existing cases
- Manual verification: Yes — replayed the failing production query with
the fixed aliasing against ClickHouse
- Edge cases covered: groupBy overlapping a counted attr; same-named
deployment across namespaces counted as distinct entities
---
### ⚠️ Risk & Impact Assessment
- Blast radius: Infrastructure Monitoring — clusters & namespaces list
APIs (counts query)
- Potential regressions: None — SQL alias rename only; scanning is
positional and result map keys are unchanged
- Rollback plan: Revert this commit
---
### 📝 Changelog
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Bug Fix |
| Description | Fixed a 500 error in Infrastructure Monitoring
clusters/namespaces APIs when grouping by an attribute that is also part
of the resource counts (e.g. node, namespace, or deployment name). |
---
### 📋 Checklist
- [x] Tests added or explicitly not required
- [x] Manually tested
- [ ] Breaking changes documented
- [x] Backward compatibility considered
## Summary
- `restructureSavedViewSpec` (migration 109) bulk-inserts legacy
`saved_views` rows into the new `saved_view` table, which has an
enforced `org_id -> organizations(id)` FK (sqlite runs with
`foreign_keys=ON`).
- Some tenants hit `constraint failed: FOREIGN KEY constraint failed
(787)` on startup because a row's `org_id` didn't match any live
organization -- e.g. an org deleted before the old table had a cascading
FK, or an install where `org_id` was never backfilled
(`015_update_dashboards_savedviews` only backfills it when there's
exactly one org).
- Fix: fetch live organization IDs up front inside the same transaction,
and skip (with a `WarnContext` log, counted in `skipped`) any row whose
non-empty `org_id` isn't among them -- same treatment as the existing
empty-`org_id` skip.
Followup to https://github.com/SigNoz/signoz/pull/12342
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
The `telemetry.*.last_observed` stats took `max()` over client-supplied
event-time columns, so a single row with a skewed or corrupt timestamp
(a 2050-dated log, a `2^32−1`-second span) poisoned them indefinitely.
### What
- Traces/logs `last_observed` now reads `max(inserted_at)` — the
collector-stamped insert time added in SigNoz/signoz-otel-collector#875;
metrics reads `inserted_at_unix_milli` (metrics migration 1007).
- Each signal checks `hasColumnInTable` first and falls back to the
previous expression, so tenants without the schema migration keep
today's behavior and switch over automatically.
### Notes
- `created_at` is unusable here: pre-migration rows evaluate its
`now64(3)` default at read time, so `max(created_at)` always reads as
"now".
- Pre-migration rows read `inserted_at` as epoch, which `max()` ignores;
the all-old case lands on the existing `Unix() != 0` skip-guard.
- Future-dated garbage never TTLs out (TTL is keyed on the event
timestamp), which is why the old stat stayed wrong once poisoned.
### Testing
- Expressions validated against `clickhouse local`, including garbage
rows (`2^64−1`, `9.3e18` ns) and empty/pre-migration tables.
- `go build`, `go vet`, golangci-lint clean.
Fixes https://github.com/SigNoz/engineering-pod/issues/5864
Deflakes the SSO login tests (`callbackauthn` and `basepath`). They all
share the `idp_login` fixture, and after it clicked Keycloak's login
button it could hand control back to the test too early — in two
different ways ([example CI
failure](https://github.com/SigNoz/signoz/actions/runs/30898802502/job/91957986941)).
### What
The fixture used to wait for the login button to disappear and treat
that as "login is done". Two things go wrong with that:
1. **The page can vanish while we're looking at it.** Asking "is the
button still visible?" takes two round-trips to the browser: find
`kc-login`, then ask whether it's displayed. If Keycloak's redirect
lands between the two, the second call is asking about a node that no
longer exists. Selenium normally recognises that as a stale element and
quietly retries — but Keycloak → SigNoz is a *same-site* hop
(`localhost` → `localhost`), where the renderer survives the swap and
that detection can miss. The raw chromedriver error (`Node with given id
does not belong to the document`) then escapes and fails the test.
That's the CI failure above.
2. **The button disappearing doesn't mean login finished.** It only
means we left the login *page*. In the SAML flow Keycloak next serves a
small auto-submitting page — still on the IdP — and *that* POST is what
actually creates the user in SigNoz. So a test could go looking for the
user before SigNoz had ever seen the callback, and fail with `User ...
not found`. Reproduces locally on `test_idp_initiated_saml_authn`.
The wait now checks what the tests actually need: **the browser has left
the IdP host** (the hostname in the URL changed) *and* the login button
is gone.
### Guardrails
- Nothing is held across the navigation — the button is looked up fresh
on every poll with `find_elements`, so "gone" is simply an empty list,
never a question asked of a dying node.
- Any browser error during a poll is read as "still navigating, try
again" instead of failing the wait.
- The wait sits through everything that's still on Keycloak (the
`login-actions` hops, the SAML interstitial) and passes only once SigNoz
has handled the callback and redirected — so the user exists by the time
the test asserts on it.
- Wrong credentials still fail loudly: Keycloak re-renders the login
form on its own host, so the wait times out exactly as before.
One change, in the shared fixture — the OIDC and SAML flows in both
`callbackauthn` and `basepath` all go through it.
### Notes
- Failure 1 needs the redirect to land inside a ~2–5 ms window of a poll
that only runs every 500 ms, so it's effectively a loaded-CI-runner
lottery — which is why it's rare and CI-only. Failure 2 shows up
locally.
- Unrelated to the PR it fired on (#12382, query-builder only); the
identical SAML test passed in the same run.
### Testing
- Reproduced failure 1 outside pytest, with a probe driving real
headless **Chrome for Testing 151.0.7922.71** (the exact build from the
CI log) through a click → POST → same-site redirect that mimics the
Keycloak login flow, with server think-time near the 500 ms poll
boundary. Both waits ran verbatim, at their real polling rate:
| Post-click wait | Logins | Failures |
|---|---|---|
| old (`EC.invisibility_of_element`) | 400 | **3 × the exact CI
inspector error** |
| new (left-the-IdP check) | 400 | **0** |
- Cross-checked the mechanism against the selenium 4.40 source with a
stubbed driver: the detached-node error does escape the old wait (it
only catches stale/not-found), while the new one absorbs it and passes
on the next poll. A bad-credentials control times out on both old and
new, so failure detection isn't weakened.
- Ran the full suites locally on the final fixture, with a fresh sqlite
+ wal store per suite (matching the failing CI leg): `basepath` 6/6, and
all 36 SSO/domain tests in `callbackauthn` — including
`test_idp_initiated_saml_authn`, which flaked with `User not found` on
the old wait in the same setup. (The one local non-pass,
`test_apply_license`, is unrelated: it asserts on wiremock's request
journal and the reused license-mock container is never reset between
runs — CI gets a fresh mock.)
- `make py-fmt` / `make py-lint` clean.
Fixes https://github.com/SigNoz/engineering-pod/issues/5850
#### Description
- `uv lock --upgrade` across the tests project: pytest 9.0.3→9.1.1, ruff
0.15.11→0.16.2, selenium 4.43→4.46, numpy 2.4.4→2.5.1, uvicorn
0.46→0.52.1, testcontainers 4.14.2→4.15.0, requests, sqlalchemy,
websockets, and the rest of the transitive set (zstandard dropped as no
longer required).
- Ignore `PLR0917` (too-many-positional-arguments), newly enforced by
ruff 0.16 — muted alongside the other `PLR09xx` complexity rules the
project already ignores (193 pre-existing hits, all in test/fixture
signatures).
#### Additional Information
- `py-fmt` (no reformats), `py-lint` (clean), and full integration-test
collection (1782 tests) pass on the upgraded toolchain. Runtime
verification against the docker stack was not run.
#### Description
- Add the fixture-vs-function rule to `.claude/rules/pytest.md`: a
fixture earns its indirection only by owning setup/teardown (`yield` +
cleanup) or provisioning a resource; a stateless action or lookup is a
plain importable function in the matching `tests/fixtures/` module
taking `signoz`/`token` as ordinary arguments.
- Apply it to the three fixture-factories introduced in #12460 that have
no lifecycle: `delete_all_dashboards` (renamed from
`wipe_all_dashboards`) and `run_query_case` are now plain functions,
their modules deregistered from `pytest_plugins`, and all call sites
updated.
- Generalize `Metrics.load_from_file` with a `label_substitutions`
parameter (placeholder rewriting, e.g. `__START_TIME__` → runtime ISO
string) and drop the bespoke `load_pods_metrics`, which duplicated the
base-time rebase logic — `02_pods.py` now loads JSONL the same way as
every other inframonitoring suite file.
Follow-up promised in
https://github.com/SigNoz/signoz/pull/12460#discussion_r3737099384.
#### Description
- Add `.claude/rules/pytest.md` with conventions for the Python
integration suite. The lead rule: **no `_`-prefixed helper functions in
test modules** — inline the logic; repetition across tests is cheaper
than indirection; genuinely shared machinery becomes a fixture. Fixtures
live in `tests/fixtures/` only, never under `integration/tests/` — with
one exception: SigNoz-level fixtures (a suite spinning up SigNoz with
different envs via `create_signoz`/`create_migrator`) always belong in
that suite's `conftest.py`. Plus: fixture-factory over indirect
parametrization, skip at collection, config via explicit `--flags`,
snake_case parametrize ids, and collection gotchas (`python_files`
prefix matching, `--import-mode=importlib`).
- Apply the no-`_helper` rule across `tests/integration`: all 40
module-level `_` helpers eliminated in dashboard, inframonitoring,
promqlconformance, querier_json_body, querierlogs, queriermetrics, and
queriertraces. Pure transforms and request wrappers were inlined at
their call sites; case-table verifier callables became data flags with
inline branches; the two 115-line resource-evolution mega-helpers folded
into parametrized tests; shared machinery moved to `tests/fixtures/` as
fixture-factories (`wipe_all_dashboards`, `load_pods_metrics`,
`run_query_case`) registered via `pytest_plugins`.
- Apply the py-comments rule across `tests/`: drop module docstrings
that restate the filename, relocate the ones carrying real constraints
next to the code they constrain, delete function/class docstrings that
restate the identifier, trim narrated steps and Args/Returns
boilerplate.
- Fix camelCase parametrize ids in `queriermetrics/01_fill.py`
(`fillGaps`/`fillZero` → `fill_gaps`/`fill_zero`).
#### Additional Information
- All 629 tests in the touched suites collect cleanly;
`py-fmt`/`py-lint`/compileall pass. Runtime verification against the
docker stack was not run.
## Summary
Adds an integration-test matrix that pins the deliberate keyless-row
contract for filter operators, independent of any feature work:
- **Negative operators are a set complement over all rows.** A row that
does not carry the key at all must match `!=`, `NOT IN`, `NOT LIKE`, and
`NOT CONTAINS`. Users opt into presence explicitly with `AND key
EXISTS`.
- **Positive operators carry an implicit existence guard**
(`FilterOperator.AddDefaultExistsFilter`), so keyless rows never
false-positive against sentinel defaults.
- **`EXISTS` / `NOT EXISTS` partition rows exactly** by key presence,
and `!= x AND EXISTS` is the documented composition for "present and not
x".
- **Numeric attributes inherit the map-default sentinel**: a missing key
reads as `0`, so `num != 5` includes keyless rows while `num != 0`
excludes them. This conflation is deliberate and pinned by name
(`numeric_neq_zero_sentinel_conflation`) as the reference point for any
value-expression change.
Coverage: 46 cases — one shared matrix over traces and logs (resource
and attribute contexts), metric labels (series without the label), the
numeric sentinel, and the EXISTS composition. The contract, matrix, seed
data, and assertions live together in one file so the contract reads top
to bottom.
## Why
These semantics were enforced only implicitly by the operator list in
`AddDefaultExistsFilter`, with no test naming the intent. That gap
allows an implementation change to alter negative-filter results
silently and lets new tests calibrate expectations against the
implementation instead of the contract. The attribute names used here
are deliberately outside every semantic-convention family, so this file
pins the base contract regardless of the semconv overlay state and
serves as the oracle that family-field behavior
(`queriertraces/13_semconv_evolution.py` in the semconv stack) must
mirror.
## Testing
- `uv run pytest --basetemp=./tmp/ --reuse
integration/tests/queriercommon/06_keyless_semantics.py` — 46/46 passed
against a stack built from main-based sources, and 2×46 against a
long-lived shared stack, confirming the set-based assertions stay stable
under environment reuse.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary
Adds the semantic-convention evolution foundation:
- vendors the OpenTelemetry schema and SigNoz overlay
- generates Go and TypeScript family tables deterministically
- exposes the Go resolver API for family members, current names, and
historical names
- adds generation checks and unit tests
Related to #6143.
## Stack
1. #12441 — Foundations (base: main)
2. #12442 — Phase 1 query (base: #12441)
3. #12443 — Phase 1 services (base: #12442)
4. #12444 — Phase 1 closure gate (base: #12443)
5. #12445 — Phase 2 signals (base: #12444)
6. #12446 — Phase 3 migration UX (base: #12445)
7. #12447 — Phase 4 rollout (base: #12446)
**Current layer:** #12441
## Testing
- `go test ./scripts/semconv`
- `go test ./pkg/types/telemetrytypes/semconv`
- `make semconv-check`
## Risk and rollback
This layer is additive apart from CI generation checks. Roll back by
reverting this PR; no stored telemetry is changed.
## Summary
- Saved views now persist a versioned, typed spec (`schemaVersion` +
`spec{compositeQuery, selectedFields, display}`) instead of a bare
composite-query blob plus an opaque, frontend-owned `extraData` string
-- mirroring the pattern dashboards already use for their v2/perses
schema.
- `/api/v1/explorer/views` keeps working exactly as before: a thin
conversion layer translates to/from the legacy wire format, including
folding `extraData`'s ad hoc JSON into the typed spec and back for
backward compatibility.
- A one-time migration rewrites existing rows into the new shape and
drops the now-unused `extra_data`/`category`/`tags` columns.
### Scaffolding decisions
- Using v2 for new handlers instead of renaming old handlers to
something else for these reasons - keep the diff minimum for easier
reviews, avoiding any git history or last updated at change in old route
registration.
- Keeping the conversion to old saved view type in handler itself rather
than `savedviewtypes` package to keep it un-exported and not let them be
available anywhere else to be used. It also enables `savedviewtypes` to
be independent on query-service models.
- Modified the existing handler and it's interface to include the v2
methods instead of adding another handlerV2 since apiserver already had
handler wired in, so don't want to pass on 2 version simultaneously.
### Breaking change
- Any unknown key in the `ExtraData` will be rejected and dropped
silently in the old APIs and give error in new version.
- If there was any way to add tag or category in saved view earlier,
that data will be lost.
- Old APIs will not support the old QB request payload, only v5 format
is supported.
---
ClosesSigNoz/engineering-pod#4651
Alternative discarded https://github.com/SigNoz/signoz/pull/12208
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
#### Description
- Remove `.github/workflows/docs.yml`, which labeled `feat:` PRs with
"docs required" and failed the check until "docs shipped" was added.
- The job is not a required status check on `main`, and nothing else
references the workflow or its labels.
Bumps `clickhouse-sql-parser` to v0.5.5, fixes the false rejection that
was left over once it landed, and closes three holes in the same
validator that the first two changes brought to light.
## The bump
**Reserved keywords as expression operands**
([#305](https://github.com/AfterShip/clickhouse-sql-parser/pull/305)).
`interval` was fixed in v0.5.4, but the same defect affected 36 other
keywords once the column appeared as an operand rather than bare.
Sweeping 94 candidates against ClickHouse 26.8.1.337, only `on` still
rejects — and ClickHouse runs that too. This one was live: `sum(limit)`
on a metric label.
**Panic on an unparseable `DEFAULT` expression**
([#306](https://github.com/AfterShip/clickhouse-sql-parser/pull/306)).
Both known cases return a parse error now instead of dereferencing nil.
The `recover` in `ErrIfStatementIsNotValid` stays — it guards the next
one of these, not these two.
[#307](https://github.com/AfterShip/clickhouse-sql-parser/pull/307) also
allows `CAST` in a table function's argument list.
## Table functions are only table functions in a table position
The parser types a call inside a table function's argument list as a
`TableFunctionExpr` as well, so the generator allow list only ever
cleared a generator whose argument was a literal. Every real dashboard
computes its row count — `numbers(greatest(1, intDiv(end_ns - start_ns,
step_ns) + 1))` — and every one was refused, on `intDiv` rather than on
`numbers`.
`TableExpr.Expr` is the only table position a SELECT can reach, so the
allow list asks that instead. Of the four places the parser builds a
`TableFunctionExpr`, two are `CREATE TABLE` paths rejected as
not-a-SELECT before the walk starts, one is `parseTableArgPrimaryExpr`,
and one is the `FROM`/`JOIN` path that wraps into a `TableExpr`.
## Three holes that were already open
Skipping argument position is only safe if nothing there can read, and
that turned out not to be true — not because of this change, but
independently of it.
**Reading functions.** `file` is both a table function and a scalar
function, and the validator never inspected scalar calls at all. On
`main` today, `SELECT file('/etc/passwd')` is accepted and returns the
file. A numeric wrapper passes ClickHouse's type check, so the row count
alone is an oracle: `numbers(length(file(x)))` yields one row per byte.
The same applies to the 42 dictionary accessors, which can be backed by
HTTP, ODBC or another database, to `catboostEvaluate`, and to the
introspection functions. All are now refused by name wherever they
appear, under `clickhouse_sql_reading_function`.
**`x IN db.table`.** ClickHouse reads this as `x IN (SELECT * FROM
db.table)`, and a qualified name on the right of `IN` parses as a
`Path`, not a `TableIdentifier` — so `SELECT * FROM t WHERE a IN
system.users` bypassed the internal-database rule entirely. Now checked,
including the `GLOBAL IN` and `NOT IN` forms.
**Quoted generator names.** The allow list matched on the formatted
name, which carries the quoting, so ``SELECT * FROM `numbers`(31)`` was
refused. It now reads the identifier the way the internal-database
branch already did.
## Effect
Replaying 72 distinct shapes of production `clickhouse_sql` that the
validator currently rejects: **64 pass, up from 59 on v0.5.4**. Two came
from the bump, three from the table-position change, and those three are
379 of the 1390 sampled occurrences. The three new rules add no false
positives to the corpus.
Of the eight left, four are correct rejections (`system` reads, `SHOW
TABLES`), one is a dashboard variable rendering as the literal `<no
value>`, one is SQL ClickHouse also rejects, and two are an open
upstream gap.
## Tests
`TestErrIfStatementIsNotValid_ShouldPassButFails` is back, holding what
remains: three forms of a parenthesised left operand of a set operator,
and `on` as a column name. It also stopped panicking — `errors.Asc`
dereferences the error it is given, so a case starting to pass took the
suite out with a SIGSEGV instead of reporting. Both refusal tables now
share one harness, bounded by the same timeout the passing table uses.
Known gap: no input is currently known to panic the parser, so the
`recover` has no test exercising it.
2026-08-07 10:39:21 +00:00
646 changed files with 33693 additions and 29885 deletions
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.
Language rules build on this one: [`go-comments`](go-comments.md), [`py-comments`](py-comments.md).
The bar is the [`comments`](comments.md) rule: nothing where the code is self-explanatory.
- **Names carry the meaning.** Make function, type, and variable names self-explanatory so the comment is unnecessary in the first place. If a comment is needed to explain what a function does, fix the name, not the comment.
- **Godoc**: Skip comments that merely restate the identifier. Document only non-obvious behavior, constraints, formats, and edge cases.
- **Generated code**: If the comment is emitted by an external codegen tool, leave it as-is — do not add or trim comments in generated files.
- **Follow the template** (`.github/pull_request_template.md`): fill in its headings (Description / Issues closed by this PR / Screenshots / Additional Information). Don't add sections the template doesn't have.
- **Keep only the headings that apply.** Delete every heading that has nothing under it, along with its `<!--...-->` placeholder comment. The body must never contain an empty heading — if only Description applies, the body has exactly that one heading.
- **Keep the description concise and human-readable.** 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.
- **Reference issues with `Closes #issue-number`** under "Issues closed by this PR" so they auto-close on merge. This goes in the PR description only — never in commit messages.
- **AI assistance in commits may optionally be disclosed with an `Assisted-by:` trailer** naming the model (e.g. `Assisted-by: Claude Opus 4.5`) — do NOT use a `Co-authored-by:` trailer for this.
The bar is the [`comments`](comments.md) rule: nothing where the code is self-explanatory.
- **Names carry the meaning.** Make function and variable names self-explanatory so the comment or docstring is unnecessary in the first place. If a docstring is needed to explain what a function does, fix the name, not the docstring.
- **No file-level docstring.** The filename says what the module is for — `tool_bin.py` gets the tool binary. A module docstring restating that is noise, and a paragraph of design prose at the top of a file goes stale where nobody is looking. A constraint belongs next to the code it constrains, not in a preamble.
- **Docstrings**: only when they say something the name and signature don't — drop them otherwise. Keep them short. A contract that genuinely needs a few lines (interacting flags, retry semantics, an edge case) is fine; a narrative is not.
- **No song and dance.** Comment the constraint or the edge case. Not the narrative, not the rationale, not what the next line does.
For the Python integration suite under `tests/`. Setup, running, and suite layout live in [`docs/contributing/tests/integration.md`](../../docs/contributing/tests/integration.md).
- **No `_`-prefixed helper functions in test modules — this is the rule that matters most.** A reader must be able to see what a test does in its body alone, without chasing private helpers that scatter the meaning across the file. Inline the logic: an expression, a comprehension, a few repeated lines are all fine — repetition across tests is cheaper than indirection. When several tests genuinely share non-trivial setup or assertions, that is what fixtures are for — in `tests/fixtures/`, see the next rule. A module-level `_helper()` is never the answer.
- **Fixtures live in `tests/fixtures/` — never under `integration/tests/`.** Not in test modules, not in suite `conftest.py` files. `tests/fixtures/` is the shared library (auth, signoz, clickhouse, logs/metrics/traces seeding, …): reuse what's there before writing anything new; when a new fixture is genuinely needed, add it to the matching `tests/fixtures/` module and register new modules in `tests/conftest.py``pytest_plugins`. **The one exception: SigNoz-level fixtures in a suite's `conftest.py`.** A suite that needs its own SigNoz spun up with different envs (`create_signoz`/`create_migrator` with `env_overrides` + `cache_key` — e.g. basepath, metricreduction, querier_json_body) keeps that in its `conftest.py`; that is always okay.
- **Fixture only when there is a lifecycle; otherwise a plain function.** A fixture earns its indirection by owning setup/teardown (`yield` + cleanup — `insert_metrics` truncating on teardown) or by provisioning a resource (containers, SigNoz instances). A stateless action or lookup (`create_saved_view`, `find_saved_view_by_name`, wiping a resource list) is a plain importable function in the matching `tests/fixtures/` module, taking `signoz`/`token` as ordinary arguments — never wrap a plain callable in a fixture-factory just to inject `signoz`.
- **Fixtures own their cleanup.** When a test needs seeded state, put the seed + cleanup pair in a fixture (`yield`, then tear down) so tests in the same suite don't interfere — the pattern `insert_metrics` sets: yield a callable, truncate on teardown.
- **Fixture-factory over indirect parametrization.** A fixture that yields a callable (e.g. `insert_metrics(metrics)`) is clearer than `@pytest.mark.parametrize(..., indirect=True)` + `request.param` — the value is an explicit argument, not resolved by magic.
- **Skip at collection, not inside the test body.** Use `pytest.param(..., marks=pytest.mark.skip(reason="…"))` so a skipped case shows as SKIPPED-with-reason **and** short-circuits before its fixtures run (no environment spin-up for a test that won't execute).
- **Test config comes from explicit `--flags`, not the environment.** Wire configuration as pytest options declared in `tests/conftest.py` (`pytest_addoption` — e.g. `--sqlstore-provider`, `--clickhouse-version`); do **not** add `os.environ` fallbacks inside tests or fixtures.
- **snake_case parametrize ids.** `ids=["fill_gaps", "fill_zero"]`, not camelCase.
- **Name suite files with the two-digit prefix (`NN_*.py`).** `pyproject.toml` restricts collection to `[0-9][0-9]_*.py` (plus the bootstrap `setup.py` / `run.py`) — a file that doesn't match is silently never collected.
- **Always run pytest from `tests/`.** `--import-mode=importlib` is what allows same-basename files across suites (`querier/01_logs.py` vs `rawexportdata/01_logs.py`), but it disables pytest's implicit `sys.path` injection — `import fixtures` only resolves via `pythonpath = ["."]` from that rootdir.
> Include screenshots or screen recordings that clearly show the behavior before the change and the result after the change. This helps reviewers quickly understand the impact and verify the update.
<!--A few plain bullets saying what changed and why, for a reviewer skimming it - not a wall of text, not a restatement of the diff, not generated boilerplate.-->
#### Description
<!--Reference issues using `Closes #issue-number` to enable automatic closure on merge. -->
#### Issues closed by this PR
> Reference issues using `Closes #issue-number` to enable automatic closure on merge.
---
<!--If applicable, include screenshots or screen recordings that clearly show the behavior before the change and the result after the change. -->
#### Screenshots / Screen Recordings
### ✅ Change Type
_Select all that apply_
<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- [ ] ✨ Feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactor
- [ ] 🛠️ Infra / Tooling
- [ ] 🧪 Test-only
---
### 🐛 Bug Context
> Required if this PR fixes a bug
#### Root Cause
> What caused the issue?
> Regression, faulty assumption, edge case, refactor, etc.
#### Fix Strategy
> How does this PR address the root cause?
---
### 🧪 Testing Strategy
> How was this change validated?
- Tests added/updated:
- Manual verification:
- Edge cases covered:
---
### ⚠️ Risk & Impact Assessment
> What could break? How do we recover?
- Blast radius:
- Potential regressions:
- Rollback plan:
---
### 📝 Changelog
> Fill only if this affects users, APIs, UI, or documented behavior
> Use **N/A** for internal or non-user-facing changes
| Field | Value |
|------|-------|
| Deployment Type | Cloud / OSS / Enterprise |
| Change Type | Feature / Bug Fix / Maintenance |
| Description | User-facing summary |
---
### 📋 Checklist
- [ ] Tests added or explicitly not required
- [ ] Manually tested
- [ ] Breaking changes documented
- [ ] Backward compatibility considered
---
## 👀 Notes for Reviewers
<!-- Anything reviewers should keep in mind while reviewing -->
---
<!--Please delete paragraphs that you did not use before submitting.-->
@@ -93,11 +99,74 @@ type GettableAuthDomain struct {
Each flavor exists for a concrete reason:
- `StorableAuthDomain` stores the typed config as an opaque `Data string` column, so the schema does not need to migrate every time a config field is added.
- `PostableAuthDomain` carries the config as a structured object (not a string) for the request.
- `UpdateableAuthDomain` excludes `Name` because a domain's name cannot change after creation.
- `PostableAuthDomain` carries the config as a structured object (not a string) for the request; `AuthDomainConfig` is a kind/spec envelope.
- `UpdatableAuthDomain` excludes `Name` because a domain's name cannot change after creation.
- `GettableAuthDomain` adds `AuthNProviderInfo`, which is derived at read time and never persisted.
The core `AuthDomain` holds the two live halves — `storableAuthDomain` and `authDomainConfig` — and owns business methods such as `Update(config)`. Conversions use the `New<Output>From<Input>` form: `NewAuthDomainFromConfig`, `NewAuthDomainFromStorableAuthDomain`, `NewGettableAuthDomainFromAuthDomain`.
The core `AuthDomain` holds the two live halves — `storableAuthDomain` and `storableAuthDomainConfig` — and owns business methods such as `Update(updatable)` and `Patch(patchable)`. Conversions use the `New<Output>From<Input>` form: `NewAuthDomainFromPostableAuthDomain`, `NewAuthDomainFromStorableAuthDomain`, `NewGettableAuthDomainFromAuthDomain`.
## Sum types: the kind/spec envelope
When a domain type is a *sum type* — exactly one of several variants, selected by a discriminator — model it as an envelope with a `kind` and a `spec`:
`Kind` is a `valuer.String` enum implementing `Enum()`; `Spec` holds exactly one concrete variant type (`BarSpec`, `BazSpec`, …). `RuleThresholdData` and `EvaluationEnvelope` in `pkg/types/ruletypes/` are the canonical in-tree examples; the dashboard panel/query/variable plugins in `pkg/types/dashboardtypes/` are the same pattern behind generics. (`QueryEnvelope` in querybuildertypes uses `type` as the discriminator key for historical reasons; new envelopes use `kind`.)
### The envelope goes at the point of variance, not the resource root
Put the envelope on the field that actually varies. The resource root is almost never a sum type — a `Foo` has a `name` and an `enabled` flag regardless of which kind it is configured with; only its configuration varies, so the envelope is the `config` field:
Hoisting `kind`/`spec` to the root would turn the whole resource into a `oneOf`: every flavor (`PostableFoo`, `UpdatableFoo`, `GettableFoo`) then needs one variant schema per kind, each repeating the common fields; every new common field has to be added to all of them; and generated clients get unions of large objects instead of one small union that narrows on `config.kind`. A root-level `kind` also collides with the resource-model meaning of the word — root `kind` conventionally answers "what resource is this" (`Dashboard`), never "which flavor of config does it hold".
The existing domains already follow this placement:
- **Rules** — plain root; envelopes on the varying fields: `thresholds: {kind, spec}` and `evaluation: {kind, spec}`.
- **Dashboards** — metadata at the root plus one typed `spec`; the unions sit deep inside, at each panel/query/variable plugin (`{kind, spec}` in `perses_plugin_wrappers.go`).
- **Saved views** — root `{schemaVersion, spec}`, where `spec` is a *versioning* envelope holding one fixed type, not a union; the unions are inside it (`spec.queries: [{type, spec}]`). Same word, different job — a versioned body is not a discriminated union.
### Why this tagging style
Of the union encodings in common use, the envelope is the *adjacently tagged* one — tag and payload side by side. Variant payloads stay collision-free, and each kind maps to a named wrapper schema that carries the discriminator, which is exactly what OpenAPI generators need. The alternatives lose on those points: *internally tagged* (`{"kind": "bar", ...fields flattened}`) mixes common and variant fields, admits cross-variant key collisions, and forces every variant schema to redeclare the discriminator; *sibling optional fields* (`{"kind": "bar", "barConfig": {}, "bazConfig": {}}`) is the anti-pattern the first rule below exists to prevent.
The rules that make the envelope work:
- **Never model variants as sibling fields.** A struct with `Bar *BarSpec`, `Baz *BazSpec` next to a discriminator cannot be expressed as an OpenAPI discriminated union, forces nilability checks on every consumer, and silently admits contradictory payloads (kind=bar with a baz spec). The chosen variant *is* the payload.
- **The envelope owns `UnmarshalJSON`.** Decode `kind` first, then switch on it to decode and validate the matching concrete type into `Spec`. Unknown kinds and missing specs are rejected at the boundary:
if err := json.Unmarshal(raw["spec"], &spec); err != nil {
return err
}
typ.Spec = spec
// ... one case per kind, default rejects ...
}
typ.Kind = kind
return nil
}
```
- **Consumers type-assert on `Spec`** (`config.Spec.(BarSpec)`) after switching on `Kind`. If assertion sites multiply, add typed accessors on the envelope (see `EvaluationEnvelope.GetEvaluation()`).
- **OpenAPI needs one unexported variant struct per kind** (`fooConfigBar{Kind; Spec BarSpec}`), exposed via `JSONSchemaOneOf()` and mapped via `PrepareJSONSchema` with the `x-signoz-discriminator` extension. The schema mechanics are covered in [handler.md](handler.md#oneof-with-a-discriminator).
- **A legacy persisted shape gets a data migration or a `StorableX`.** When rows were written before the envelope existed, prefer an idempotent `sqlmigration` that rewrites them into the new shape, so the storable type simply nests the envelope. Only when the old shape must keep being written (external writers, rollback windows) keep it in a storable twin and convert at the type boundary.
## Conventions that tie the flavors together
@@ -139,6 +208,8 @@ Both are optional. Do not introduce them if `PostableX` already covers the case.
- Every domain package defines the core type `X`. Only `X` is mandatory.
- Add `PostableX` / `GettableX` / `UpdatableX` / `StorableX` one at a time, only when the shape actually diverges from `X`.
- Model sum types as a `{kind, spec}` envelope with a validating `UnmarshalJSON` — never as sibling variant fields next to a discriminator.
- The envelope goes on the field that varies, never at the resource root — common fields stay on the resource, outside the union.
- Domain logic lives on `X`, not on the flavor types.
- Conversions can be a `New<Output>From<Input>` constructor or a receiver-style `ToY()` method — pick whichever reads best at the call site.
- Use a type alias when two shapes are truly identical.
returnnil,errors.New(errors.TypeLicenseUnavailable,errors.CodeLicenseUnavailable,"a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
returnnil,errors.New(errors.TypeLicenseUnavailable,errors.CodeLicenseUnavailable,"a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
// Wrapping adds a DOM element, which can turn into a flex/grid item or
// break `> *` and `:nth-child` selectors, so it is offered as a suggestion
// (`--fix-suggestions`) rather than applied by a bare `--fix`.
hasSuggestions:true,
messages:{
'conditional-text-node':
'Conditionally rendered text nodes with siblings, rendered as a direct child of a JSX element, must be wrapped so Google Translate cannot break React\'s DOM: `<span className="translate-safe">{value}</span>`. Translate replaces the bare text node with a `<font>` element and React then throws on `removeChild`. This also applies to values returned from functions, so `getString()` becomes `<span className="translate-safe">{getString()}</span>`.',
'text-node-preceded-by-conditional':
'Text nodes which are preceded by a conditional expression, rendered as a direct child of a JSX element, must be wrapped so Google Translate cannot break React\'s DOM: `<span className="translate-safe">text</span>`. Translate replaces the bare text node with a `<font>` element and React then throws on `removeChild`.',
},
},
createOnce(context){
constsuggestWrap=(build)=>[{desc:'Wrap in a <span>',fix:build}];
'React components should avoid returning text nodes directly (or numerical values which will be rendered as text). When a React component returns values other than JSX / null, Google Translate can continue to display stale values after state changes, without any error being thrown. Since this is very hard to debug it is better to avoid it altogether.',
// Wrapping changes what the component renders, so it is offered as a
// suggestion (`--fix-suggestions`) rather than applied by a bare `--fix`.
hasSuggestions:true,
messages:{
'return-value-is-text-node':
'React components should avoid returning text nodes directly (or numerical values which will be rendered as text). When a React component returns values other than JSX / null, Google Translate can continue to display stale values after state changes, without any error being thrown. Since this is very hard to debug it is better to avoid it altogether.',
replaysSessionSampleRate: 0.1,// This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
replaysOnErrorSampleRate: 1.0,// If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
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.