Compare commits

..

52 Commits

Author SHA1 Message Date
Ashwin Bhatkal
8485ffcb2c docs(skills): a scoped noise run marks every story it omits as flaky 2026-10-21 00:44:45 +05:30
Ashwin Bhatkal
c213175c26 docs(skills): measure noise only on the pairs that moved 2026-10-21 00:29:51 +05:30
Ashwin Bhatkal
347ef8633e docs(skills): point the story verify notes at the diff skill 2026-10-21 00:11:14 +05:30
Ashwin Bhatkal
094c7b1cfd docs(skills): say the screenshot diff rules in plainer words 2026-10-20 23:56:00 +05:30
Ashwin Bhatkal
fa3eb5bb94 docs(skills): say the permission story rules in plainer words 2026-10-20 23:47:44 +05:30
Ashwin Bhatkal
654d7fe989 docs(skills): generalise the screenshot diff guidance and require stories before capturing 2026-10-20 23:32:55 +05:30
Ashwin Bhatkal
1ddfa3179b docs(skills): add the sbshot visual diff skill and fix the vacuous assertion advice 2026-10-20 23:17:27 +05:30
Aditya Singh
fe24e1e131 fix(exceptions): keep the filter pills when running the query (#13204)
#### Description
- On the exceptions page, Run Query cleared the filter pills from the
search bar. The list and the count then loaded unfiltered.
- The shared run handler empties `filters.items`. The exceptions filter
keeps its state only in `filters.items`. #8733 made this handler run for
every caller.
- Run Query on exceptions and Ctrl+Enter in the search bar now keep the
filter items. This PR does not change the shared run handler, so the
explorers work the same.

#### Issues closed by this PR

Closes https://github.com/SigNoz/events-pod/issues/186

####  Screen recording


https://github.com/user-attachments/assets/3fae4f8e-2916-42f5-b2cf-56e7e376a451



https://github.com/user-attachments/assets/cc076823-7302-4eb4-99f7-9e4375a1a24f





#### Additional Information

The other pages with this search bar (funnels, pipelines) keep their
filters in their own state, so the new Ctrl+Enter changes nothing there.
2026-10-20 17:28:55 +00:00
Ashwin Bhatkal
67de630d03 feat(authz): add fine-grained authorization for notification channels (#12975)
#### Description

Puts the notification channel screens on fine-grained authz. Stacked on
#12976, which moves them onto the v2 API without touching authorization.

- `notification-channel` was already enforced on the v2 routes but never
reached the frontend: `cmd/authz.go` keeps an allowlist of the resources
exposed to the generated permission types, and channels were missing
from it. Adding it regenerates `permissions.type.ts`, and the panel
entry makes the resource show up when editing a custom role.
- Adds the permission builders (`list`/`create` at the collection,
`read`/`update`/`delete` per channel) and the two hooks that read them,
mirroring the dashboard helpers.
- Swaps the role checks for those permissions on the list, the create
button, the per-row actions, the form and the create/edit routes.

#### Issues closed by this PR

Closes https://github.com/SigNoz/pulse-pod/issues/290

#### Screenshots / Screen Recordings

[Before and after, every channel
screen](https://claude.ai/artifact/XGtzyX1B7ipNhdpMkJJEW4): swipe, fade
or side-by-side between #12976 and this branch, one entry per Storybook
story.

19 new permission states. The other 38 screens are pixel-identical, so
nothing changes for a user who holds the permissions. The single
exception is the list's loading state, where the skeleton is now inset
to line up with the header band above it.

#### Additional Information

- `/alerts/channels/new` and `/alerts/channels/edit/:id` were ADMIN-only
through the legacy role gate. They now run on authz, so a custom role
granting `notification-channel:create` or `update` can reach them;
`Private.test.tsx` is updated to match.
- The verbs match what the routes enforce and what the managed roles
already grant: admin gets create/read/update/delete/list, editor and
viewer get read/list. `test` is deliberately gated on `create`, since a
test send persists nothing and the channel need not exist.
- With only `read` and `list`, the edit screen opens read-only rather
than disappearing.
- Generated file: `permissions.type.ts` is produced by `go run
./cmd/enterprise generate authz` and should not be hand-edited.
2026-10-20 16:43:32 +00:00
Ashwin Bhatkal
dbd456b234 feat(notification-channels): move the channels UI onto the v2 API with a redesigned list (#12976)
#### Description

- Moves the notification channel screens onto
`/api/v2/notification_channels` (list, get, create, update, delete,
test) through the generated client. The per-kind create/edit/test flows
are replaced by one form driven by the generated spec types.
- Redesigns the channels list on the shared table the rest of the
product uses. Search, kind filter and page live in the URL, so a
filtered list survives a reload and can be shared.
- Surfaces the configuration v2 added: Slack `color`, `titleLink`,
`pretext`, `fallback`, `footer`, `fields[]` and `actions[]`; webhook
`bearerToken`, with basic auth now genuinely optional.
- Adds a read-only JSON view of a channel, with credential fields masked
behind a reveal toggle.
- Points the alert rule and routing policy channel pickers at the v2
list. It pages rather than truncating, so an org past the API's 200
limit no longer loses channels from the dropdown.
- Mounts the create and edit routes inside the alerts page, so opening a
channel is a normal client-side navigation instead of a full remount.

#### Issues closed by this PR

Closes https://github.com/SigNoz/pulse-pod/issues/415

#### Screenshots / Screen Recordings

[Before and after, every channel
screen](https://claude.ai/artifact/4bQPQRXmDxCUPVXEvajT2h): swipe, fade
or side-by-side between `main` and this branch, one entry per Storybook
story.

31 screens changed and 8 are new. The `Viewer` list state is gone:
read-only stopped being a role the list checks and became a permission,
so those states live on #12975.
2026-10-20 16:43:31 +00:00
Srikanth Chekuri
9af1407294 chore(openapi): keep field descriptions on referenced properties (#13194)
#### Description

- A `description:"..."` tag on a struct field whose type is a named
schema (an enum or another struct) never reached `docs/api/openapi.yml`:
OpenAPI 3.0 allows no siblings next to `$ref`, so the reflector emitted
a bare reference. The existing tags on `thresholdBasic.kind`,
`evaluationRolling.kind`, the query envelopes and the authn domain
configs were all lost this way.
- Adds a `jsonschema.InterceptProp` hook that wraps such references in
`allOf: [{$ref}]` and keeps the description beside it, the same shape
openapi-go already emits for deprecated references. Fields without a
description keep the bare `$ref`.
- Regenerates the spec and the frontend API clients. Only descriptions
were added; discriminators and mappings are unchanged, and oapi-codegen,
orval and the MCP server's schema extraction all resolve a single-ref
`allOf` to the referenced type.
- Groundwork for adding field descriptions to the rule, query builder,
notification channel, saved view and dashboard payloads in follow-up
PRs, one per package. Tracked in SigNoz/nerve-pod#426.

#### Issues closed by this PR

Part of SigNoz/nerve-pod#426.

#### Additional Information

Verified with `go test ./pkg/signoz/`, `go run cmd/enterprise/*.go
generate openapi` and `pnpm generate:api`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-20 16:21:09 +00:00
Vinicius Lourenço
2cd7285e3e chore(audit): address tinypool audit (#13203)
Some checks failed
build-staging / staging (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
<!--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

A tiny PR to address vanta reports, I tiny-pool was being used by
oxlint/oxfmt, so I upgraded both.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR

<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information

Some files changed due to upgrade in the deps of oxfmt, and I also had
to disable react compiler since it's now supported by oxlint by default.
2026-10-20 13:02:23 +00:00
Ashwin Bhatkal
63924a0fbb refactor(alerts): read the channel pickers from the v2 list API (#13129)
#### Description

The channel pickers on the alert rule form and the routing policies read
`/api/v2/notification_channels` instead of the hand-written v1 client.

- `useChannelOptions` pages the v2 list rather than truncating, so an
org past the API's 200 limit stops losing channels from the dropdown.
- It always returns an array, so the call sites drop their `|| []`
fallbacks.
- The story mocks for the pages that render a picker move to the v2
endpoint.
2026-10-20 12:57:46 +00:00
Ashwin Bhatkal
9127c5abf7 feat(notification-channels): rebuild the channels list on the v2 API (#13128)
#### Description

The channels list, on `/api/v2/notification_channels` and the shared
table the rest of the product uses. Split out of the v2 form migration
so each can be read on its own; the form keeps running on v1 until that
lands.

- The list reads the v2 list endpoint: search, kind filter and page live
in the URL, so a filtered list survives a reload and can be shared. Row
actions open, duplicate-free delete, and a read-only JSON view of a
channel with credentials masked behind a reveal.
- Create and edit now mount inside the alerts page, so opening a channel
is a client-side navigation instead of a full remount that resets the
tab.
- The alert rule and routing policy pickers read the same v2 list. They
page rather than truncating, so an org past the API's 200 limit stops
losing channels from the dropdown.

#### Additional Information

The v1 and v2 APIs share the same row and id, so the list links straight
at the existing edit screen and it resolves the channel unchanged.

Nothing here deletes the old list; it stops being rendered and is
removed once the rest of the stack lands.
2026-10-20 12:57:45 +00:00
praneeth-signoz
37f3cd4403 feat(alerts): Support telegram channel for alerts (#13074)
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description

- Adds Telegram as a custom notifier so a rule's `_title_template` /
`_body_template`
annotations override the channel's message, matching the other custom
channels.
- Telegram's HTML parse mode accepts a fixed tag list (tags like p , br,
div , span...etc not supported)and the Bot API can reject the entire
sendMessage on anything outside it — swapping in the generic HTML
renderer stops notifications arriving at all. Hence a dedicated renderer
that emits only supported tags, flattening headings, lists, tables and
images to text.

Sample payload — what the generic renderer puts on the wire

```
{
  "chat_id": "8862729750",
  "parse_mode": "HTML",
  "disable_web_page_preview": "true",
  "text": "<b><p>renderer test (critical)</p></b>\n\n<p><strong>firing</strong> on <code>used</code></p>\n<h2>Impact</h2>\n<ul>\n<li>memory at 0.39</li>\n</ul>"
}
```

Against the same rule, the Telegram renderer sends:

```
{
  "chat_id": "8862729750",
  "parse_mode": "HTML",
  "disable_web_page_preview": "true",
  "text": "<b>renderer test (critical)</b>\n\n<b>firing</b> on <code>used</code>\n\n<b>Impact</b>\n\n• memory at 0.39"
}
```

- Verified against a private chat (positive chatId), a group (negative
chatId), and a specific forum topic within that group via
messageThreadId.
- Fixes a ruler bug: the expander rewrote every `$`-ref into a label
lookup,
blanking private annotations before any notifier saw them. Per-rule
templates
  never worked end to end for any channel until now. 
Check the screenshot of how it looks without the fix and with the fix
for the same template

- Drops `parseMode` from the Telegram channel spec. HTML is the only
mode rendered
for, and the notifier and the channel read path both reject anything
else.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR

Closes https://github.com/SigNoz/pulse-pod/issues/509

#### Screenshots
<img width="250" height="550" alt="File"
src="https://github.com/user-attachments/assets/2d568a41-2d96-4fee-9575-a5867e34c804"
/>

<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information

- `parse_mode` still appears in `openapi.yml` under
`ConfigTelegramConfigDTO`.
That's upstream's `config.TelegramConfig`, a different schema from ours.
- `$alert.value`, `$rule.threshold.value`, `$rule.compare_op` and
`$rule.match_type` render empty because no rule type attaches those
annotations.
Attaching them adds rows to MS Teams cards and values to Opsgenie
descriptions,
  so it's left as a follow-up.

---------

Co-authored-by: Naman Verma <naman.verma@signoz.io>
2026-10-20 11:00:20 +00:00
Ashwin Bhatkal
65fc5a9cbb ci(e2e): stop label events from failing the e2e check (#13167)
Some checks failed
build-staging / staging (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
#### Description

- Adding any label to a PR left failed `e2e` checks next to the real
result. A `pull_request_target` run failed as unauthorized, and the
`labeled` run cancelled the in-flight e2e run, whose `e2e` job then
reported failure.
- e2eci now runs only on `pull_request` opened / synchronize / reopened,
so each commit gets its `e2e` check from a single run.
- Labels are read live in the `changes` job. The new `e2elabelci`
workflow re-runs the commit's e2eci run when `safe-to-test` or
`safe-to-e2e` is added, so the labels still start e2e right away without
adding a second `e2e` check. It never checks out PR code.
- Fork and dependabot PRs still need `safe-to-test`, and run only on a
re-run, because `postci` strips the label on push and a first attempt
could read it before that lands.
- Fork e2e now runs the PR's code under `pull_request` with no secrets.
Under `pull_request_target`, checkout defaulted to the base branch, so
fork runs tested `main` instead of the PR.

---------

Co-authored-by: Aswin <aswin.km@signoz.io>
Co-authored-by: Aswin K.M. <astrobot7@gmail.com>
2026-10-19 15:29:43 +00:00
Aditya Singh
03bdeb9ba4 feat(saved-views): turn on saved views and the bottom strip (#13125)
Some checks failed
build-staging / staging (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
#### Description
- This PR turns on saved views and the bottom strip for all users. The
`SAVED_VIEW_ENABLED` flag now always reads as on.
- The flag code stays in place. The cleanup in
https://github.com/SigNoz/events-pod/issues/77 removes it.
- The old saved views URLs now redirect to their explorer through the
existing old-routes redirects:
  - `/logs/saved-views` to `/logs/logs-explorer`
  - `/traces/saved-views` to `/traces-explorer`
  - `/metrics-explorer/views` to `/metrics-explorer/explorer`
  - `/meter/explorer/views` to `/meter/explorer`

#### Issues closed by this PR

Part of https://github.com/SigNoz/events-pod/issues/57

#### Additional Information
- Merge this PR after #13022, #13028 and #13027. Without them, the
explorers show a placeholder views panel next to the old bottom bar.
- The flag also hides the Pylon chat bubble and the chat support
gateway, because Support moves into the bottom strip.
2026-10-19 13:52:25 +00:00
Aditya Singh
e4e58e2349 fix(explorer): stop reporting dropped order by to sentry (#13153)
#### Description
- The explorer order-by sanitizer sent a Sentry warning each time it
dropped an order-by column. Each new order made a new Sentry issue, and
each new issue paged.
- Dropping a stale order is expected. Since #13085, it happens when a
user switches from List to Time Series or Table.
- The sanitizer no longer reports to Sentry. The filtering is unchanged.

#### Issues closed by this PR

Closes https://github.com/SigNoz/events-pod/issues/180
2026-10-19 11:03:04 +00:00
Gaurav Tewari
6c1f12f5d0 feat(trace-details): waterfall tooltip redesign and AI span badges (#13066)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
<!--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

- Redesigned span tooltip with AI usage breakdown; tooltip rows/styles
shared under `TraceTooltip`.
- AI badge and icons on gen-ai spans in the waterfall.
- Header shows trace-level AI tokens (with breakdown tooltip) and cost
from the summary's `ai` field.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Close -
https://github.com/orgs/SigNoz/projects/59/views/18?pane=issue&itemId=263424271&issue=SigNoz%7Cnerve-pod%7C397

<!--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/b22bcbfb-8f74-40ac-a263-d5ac2a1f23b0


TDD-
https://app.notion.com/p/signoz/Trace-Details-Changes-for-AI-o11y-3ebfcc6bcd1980b185b4c17cd1810075?source=copy_link



<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
figma -
https://www.figma.com/design/UPcVP7G1Scd5PLOQzvI9Q1/Trace-Explorer?node-id=4874-3499&t=5QH0Uv1oiS7v1Rfe-0

- We changed tooltip designed a little bit, we added same font of
subheading ( usage breakdown ) as of heading (span) , min and max
heights also cost icon could be better .
- We have introduced a new logic to calculate total 
```
if input < cache_read + cache_write:
  total_input = input + cache_read + cache_write
else
  total_input = input
  
```
- Also now if backend sends any of ai related field we will display all
field with value set as zero .
- We have updated events tooltip as well (styles ). 
- have added storybook as well . pages-traces-trace-details--ai-trace

-  Span detail changes will be done in  last pr :) 
Stack 2/4, based on #13065.

<!--Please delete paragraphs that you did not use before submitting.-->

---------

Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
2026-10-19 05:52:16 +00:00
Aditya Singh
6c6632f940 test(saved-views): authz stories for each missing permission (#13172)
Some checks failed
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / staging (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
#### Description
- The saved views stories now have an `Authz` folder, with one story per
missing permission. The stories follow the dashboard and billing authz
stories.
- `NoList`, `NoCreate`, `NoRead`, `NoUpdate` and `NoDelete` each deny
one permission.
- `NoCreateSaveMenu`, `NoUpdateSaveMenu` and `NoReadOpenView` show the
same denials in the `✓` menu and on an open view.
  - `ReadOnly` denies create, update and delete together.
- `CheckLoading` and `CheckFailed` cover a pending check and a failed
check.
- Each story checks what is disabled and what still works.
- The main stories move to `Components/Saved Views/Overview`, and
`NoAccess` is now `NoList`.
- The header `✓` and `✕` buttons get a gap again. After the ui upgrade
they are link buttons without padding, so they touched.

#### Issues closed by this PR

#### Screenshot

Before

<img width="608" height="120" alt="image"
src="https://github.com/user-attachments/assets/e9fcf890-3355-4cd5-82e4-a4e0221aaacd"
/>


After

<img width="734" height="212" alt="image"
src="https://github.com/user-attachments/assets/7ce0d2d3-7155-4365-bbab-aad0fef6fc29"
/>


Part of https://github.com/SigNoz/events-pod/issues/71
2026-10-16 19:26:53 +00:00
Aditya Singh
39b752c428 feat(logs): move the list row filter chips off v3 attribute keys (#13148)
#### Description

- The "Add to query" chip on a logs list row no longer fetches v3
`attribute_keys` before it adds the filter. The hook builds the filter
item with the same util the log details drawer uses, then redirects. The
stored key has an empty data type and context. The filter expression is
built from key and value, so the request is unchanged.
- `LogDetail` dropped two props it never read, `onAddToQuery` and
`onClickActionItem`, from its type and from six call sites.
- Removed the code that only existed for those props: the `EntityLogs`
callback, `generateFilterQuery`, `getOldLogsOperatorFromNew` and
`ActionItemProps`. Removed `onGroupByAttribute` from `useActiveLog`,
which nothing called.

#### Issues closed by this PR

Closes https://github.com/SigNoz/events-pod/issues/160


#### Screenshot



https://github.com/user-attachments/assets/7cce500a-6dbf-45cb-937f-7366d515b0b9



https://github.com/user-attachments/assets/f5cbd1bd-0863-4633-ad6e-2fd15f7ae7c5



https://github.com/user-attachments/assets/2fb7dbeb-c474-4730-9a9d-363257a7fff7



#### Additional Information

- Only one user path runs the changed hook: the field chips on a log row
in the logs explorer list and in live logs.
- The merged where clause shows adjacent clauses without `AND`. The
grammar allows an implicit AND and the query runs. This is pre-existing
in `convertFiltersToExpressionWithExistingQuery` and is not changed
here.
- Part of https://github.com/SigNoz/events-pod/issues/156.
2026-10-16 18:32:25 +00:00
Aditya Singh
dccd75e1de feat(saved-views): gate saved views on their permissions (#13027)
#### Description
- Saved views now follow the `saved-view` permissions:
  - `list` gates the views panel.
  - `create` gates `+`, "Save as new view" and "Create view".
- `read` gates opening a view. List rows for views the user cannot read
are disabled, with the reason on hover.
  - `update` and `delete` gate the row menu and the `✓` menu, per view.
- Opening a view checks `read` first. Without it, the view is not
fetched and the header shows "No view access".
- Denied buttons and menu items stay visible. They are disabled and show
the reason on hover. They use `AuthZButton` and `AuthZDropdown`.
- The row menu has "Copy ID", so a user can copy a view's id to use in a
role.
- The authz generator now includes `saved-view`, and roles settings gets
a Saved Views panel.
- Stories for the header and the views panel are under `Components/Saved
Views`.

#### Issues closed by this PR

Closes https://github.com/SigNoz/events-pod/issues/76


#### Screenshot / Screenrecording



https://github.com/user-attachments/assets/c6505fb1-5a57-4c61-ad7e-09531d500002

<img width="490" height="982" alt="Screenshot 2026-10-15 at 12 13 00 PM"
src="https://github.com/user-attachments/assets/b8ab7760-c0c9-4ee7-8201-c8dae8cdfd57"
/>


<img width="349" height="567" alt="Screenshot 2026-10-15 at 12 13 32 PM"
src="https://github.com/user-attachments/assets/ee7e0ebc-e929-4ff1-a624-e605fe09cb95"
/>


<img width="693" height="955" alt="Screenshot 2026-10-15 at 12 14 10 PM"
src="https://github.com/user-attachments/assets/023c022a-5105-471a-acce-835251f9ea3f"
/>


<img width="1458" height="2016" alt="image"
src="https://github.com/user-attachments/assets/c33d5e65-dae7-454e-a5e0-2b7796e204cc"
/>


#### Additional Information
- The change is behind `SAVED_VIEW_ENABLED`.
- On staging, the per view update and delete checks do not show in the
AuthZ DevTools. List and create work. The backend team must check this
before the PR moves out of draft.
- The save as new prompt and the unsaved changes nudge are not in this
PR.
2026-10-16 17:32:31 +00:00
Nikhil Mantri
4f3e7fa50e feat(ruler): add clone alert rule API (#13093)
#### Description

- Adds `POST /api/v2/rules/{id}/clone`, a server-side clone for alert
rules. The caller passes only the source rule's id and gets back the
newly created rule.
- Until now, cloning was done entirely by the frontend, which re-posted
the whole rule it had in memory to the create endpoint. The new endpoint
copies the rule from what is actually persisted, so clones are
consistent regardless of the client.
- The clone keeps every modeled field of the source, including its
enabled or disabled state, and is owned by the user who cloned it.
Cloning fails with a 400 that names the source rule when it no longer
passes create validation, for example when its channel has been deleted.
- The name follows the convention introduced by the dashboard clone API:
`My Alert - Copy`, then `My Alert - Copy (2)`, `My Alert - Copy (3)`
when a copy is cloned again. The same suffix logic was adopted, minus
the length cap, since alert names have no limit today.
- The endpoint reuses the existing create path, so validation,
notification routing and evaluator start-up behave exactly as a manual
create.
- Includes unit tests for the naming and copy logic and an integration
test covering clone, clone of a clone, unknown id and malformed id.

#### Issues closed by this PR

Closes SigNoz/pulse-pod#228

#### Additional Information

- Backend only. The frontend still uses the client-side approach and can
switch to this endpoint in a follow-up.
- Names are not de-duplicated against existing rules, matching the
dashboard clone behaviour. Rule names are not unique today, so a
collision is harmless.
2026-10-16 17:23:32 +00:00
Nityananda Gohain
531504c523 fix(tracedetail): keep litellm tool calls and AI SDK JSON answers, mark redacted media (#13166)
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
- AI SDK: a JSON answer such as `{"answer": 42}` was dropped, and a JSON
list was read as nameless tool calls. The output is now tool calls only
when every item has `toolName`;
  anything else is kept as text.
- litellm: parallel calls (`gen_ai.completion.N` numbers tool calls, not
choices), a call next to text, and the history's `tool_calls` /
`tool_call_id` kept beside `parts` now become tool call and tool
response parts.
- Redaction placeholders (OpenInference's `__REDACTED__`, OpenRouter's
`[OR_REDACTED]`) no longer become `uri` parts: media without a URL
scheme is a generic part, with a warning.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of
https://github.com/SigNoz/nerve-pod/issues/266#issuecomment-6198620761

<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- Checked against real spans from litellm 1.105.1, OpenInference 0.1.49
and AI SDK 5.0.277 run against a local stub server. Replaying the
static-telemetry-generator captures changes only the four affected
spans.
- OpenInference redacts `input.value` too, so a redacted image cannot be
recovered from the raw request.
2026-10-16 14:23:02 +00:00
Nikhil Soni
b770c249a0 feat(querier): read traces json numbers as toFloat64OrNull and index that (#13101)
#### Description

- The querier reads a traces JSON number as
`toFloat64OrNull(p::String)`: every numeric type and a numeric string
read as Float64. Replaces the `dynamicType` gate around
`accurateCastOrNull`.
- The `toFloat64` collision cast leaves a read already starting
`toFloat64OrNull(` bare.

Why avoiding the `toFloat64` wrapper on json number queries?
The wrapper exists for the map (`Bad get: has UInt64, requested
Float64`) and hid the read from its skip index.



#### Additional Information

- A number-typed key now matches a value stored as a numeric string
(`"404"`), and an integer above 2^53 reads rounded instead of NULL.

#### Performance Impact
Almost none

```
case: num = all values already numbers
case: mixed = values are numbers and stringified mixed
rows: 20M
┌──────────────────────────────────────────────────────┬───────┬─────────────┬─────────┐
│                         Read                         │  num  │    mixed    │   ≈     │
│                                                      │       │             │ ns/row  │
├──────────────────────────────────────────────────────┼───────┼─────────────┼─────────┤
│ new toFloat64OrNull(p::String)                       │ 0.56s │ 0.56s       │ 28      │
├──────────────────────────────────────────────────────┼───────┼─────────────┼─────────┤
│ old dynamicType gate (the QB wrapped it in           │ 0.57s │ 0.77s       │ 29 / 38 │
│ toFloat64)                                           │       │             │         │
└──────────────────────────────────────────────────────┴───────┴─────────────┴─────────┘
```
2026-10-16 14:04:08 +00:00
Nikhil Soni
6e6d7ebe8e feat(promote): index materialized paths on their json sub-columns (#13054)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
## Description

- Adds `POST /api/v1/promoted_paths/materialized`: indexes the JSON
sub-columns of the signal's materialized attributes whose materialized
column has a skip index, and returns the indexed paths and the skipped
ones (already indexed).
- Each JSON index mirrors the type and granularity of the materialized
column's index, typed by the column (`String` or `Float64`), and is
built on new parts only. Reruns are no-ops.
- Only signals with a JSON attribute target are accepted (traces today),
so `logs` works once its attributes get a JSON target. Authz checks the
update scope of that signal.

## Additional Information

- The traces querier reads numbers through a `dynamicType`-gated cast,
so `Float64` sub-column indexes are not used until the querier supports
them.
- A materialized key that can't be indexed (unsupported index type,
bool, cardinal path) is logged and left out.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:33:51 +00:00
Abhi kumar
aef60bad5d feat(charts): mark partial time buckets with a dotted ring (#13165)
#### Description

- The last (and first) bucket of a time series is often still filling.
The API already flags these with `partial: true`, but charts drew them
as normal points, so panels showed a fake drop at the right edge
(customer report: 1,575 → 281).
- Time series and Area panels (V1 and V2 dashboards) now draw a dot with
a dotted ring on partial points; Bar panels get a dotted outline around
the partial bar. Lines and bars are otherwise unchanged.
- The tooltip header shows "(partial)" when hovering a partial bucket.
- The flag is carried through the existing data paths
(`flattenTimeSeries` for V2; converter → `convertNewDataToOld` →
`QueryData.partialTimestamps` for V1) into the shared uPlotV2 series
config, where a draw hook renders the markers. Charts that don't pass
partial timestamps are unchanged.

#### Screenshots

<img width="790" height="502" alt="image"
src="https://github.com/user-attachments/assets/15779208-2143-44c4-993e-49d4cd5a3a79"
/>
<img width="1034" height="734" alt="image"
src="https://github.com/user-attachments/assets/9e968a4b-b554-4fac-a57f-46afa94f7e4f"
/>


#### Additional Information

- Metrics queries round the end time down to the minute, so their last
bucket is rarely partial. Traces/logs panels show the marker most
readily.
- The bar outline re-runs that series' bar path builder for the one
partial bar to get its rect (the width calc scans the series once per
partial bar per redraw).

Closes https://github.com/SigNoz/pulse-pod/issues/592
2026-10-16 11:53:14 +00:00
Jugal Kishore
7d77577d75 fix(onboarding): link docs by final URL and dedupe swift data source (#13161)
#### Description

- Point 22 onboarding doc links to their final URLs instead of paths
that only resolve through signoz.io redirects.
- Rename the iOS Swift card's `dataSource` to `ios-swift`; it shared
`swift` with the Swift APM card, so `?getStartedSource=swift` and card
selection could not tell them apart.
- Recolor or trim 13 logos that were invisible or unreadable on the dark
or light card (Render, CloudNativePG, LiveKit, Apache Druid, HCP Vault,
Heroku, Podman, GitHub, Celery, OpenCode, Vector, Baseten, Honeycomb).

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/echo-pod/issues/796

<!--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.-->

---------

Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-16 10:09:41 +00:00
Vikrant Gupta
f1bf33630f feat(authz): enable FGA for infra monitoring routes (#13143)
#### Description

- Infra monitoring routes were still on the coarse `ViewAccess` gate, so
a custom role scoped to a workspace key on metrics could read every
host, pod and workload through them while being denied on `query_range`.
- All 12 routes now declare a telemetry resource def: lists derive the
resource from the caller's `filter.expression` exactly as `query_range`
does, checks resolves to the metrics wildcard. Managed roles are
unaffected; key-scoped custom roles get 403 unless the filter pins a
granted key.
- Adds four generic extractors in `querybuilder` (filter-expression
body, wildcard, raw stream, builder query) that the other telemetry
surfaces will reuse in follow-up PRs.

#### Issues closed by this PR

Part of SigNoz/keystone-pod#141
2026-10-16 09:33:07 +00:00
Gaurav Tewari
c1cc58ac50 feat: use allow list in getSpanDisplayData (#13124)
<!--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

- Span details (pretty and JSON views) now shows an allow list of
queryable span fields instead of hiding a deny list.
- Hides rendering-only keys: `time_unix`, `level`, `has_children`,
`sub_tree_node_count`, `service.name`, and the
`formattedInput`/`formattedOutput`/`formatter`/`formatterWarnings`
fields from #13121.
- `attributes` and `resource` are shown in full.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
https://github.com/SigNoz/nerve-pod/issues/432 

<!--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="1637" height="1092" alt="image"
src="https://github.com/user-attachments/assets/fd7a3af8-c558-49d2-b120-ffbddf24924a"
/>
now - 
<img width="1683" height="1094" alt="image"
src="https://github.com/user-attachments/assets/01d18ac6-4b75-4d42-9c16-60d6f1911723"
/>


<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information

<!--Please delete paragraphs that you did not use before submitting.-->

https://github.com/SigNoz/nerve-pod/issues/432#issuecomment-6164009733

---------

Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
2026-10-16 06:39:46 +00:00
Pandey
c2dd94f190 fix(alerts): drop vulnerable v1 rule history endpoints (#12163)
#### Description

- Remove `POST
/api/v1/rules/{id}/history/{stats,timeline,top_contributors,overall_status}`.
They built ClickHouse queries from the path `{id}` and body fields via
`fmt.Sprintf`, allowing SQL injection by any authenticated user.
- Superseded by the parameterized `/api/v2/rules/{id}/history/*`
handlers; the frontend already moved to v2 in #12132.
- Drop the now-unused v1 rule state history reader methods and model
types, including `GetLastSavedRuleStateHistory`, another injection sink.

#### Issues closed by this PR

Closes #11747

#### Additional Information

Breaking: v1 rule history endpoints are gone. Frontend builds predating
#12132 still call them, so the alert history page breaks until they are
upgraded.
2026-10-16 06:00:15 +00:00
Nityananda Gohain
60bc6ac96f feat(tracedetail): format raw Anthropic and Gemini bodies, skip embeddings (#13141)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
<!--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
- Anthropic Messages: formats a request `{system, messages}` or a
response `{content, stop_reason}`, with every image and document source
type, `tool_use`, `tool_result`, thinking and the server tool blocks.
- Gemini generateContent: formats a request `{contents,
systemInstruction}` or a response `{candidates}`, in REST camelCase or
SDK snake_case, with inline and file data, thought parts, `functionCall`
and `functionResponse`.
- Tool results that both APIs carry inside the next user turn become a
tool message before it. The Anthropic stop reasons and Gemini finish
reasons map onto the schema's enum.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/nerve-pod/issues/266


<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
Eighth and last of the stack, on top of #13115. No capture records
either body, OpenInference's flattened messages cover its users; every
case cites the SDK type or API doc behind it. These serve gateways
recording raw provider bodies and the fallback path.
2026-10-15 19:58:59 +00:00
Nityananda Gohain
8c4db94ef7 feat(tracedetail): format OpenRouter answers and Vercel AI SDK prompts (#13139)
<!--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
- OpenRouter: formats the `{completion, reasoning, tools, rawRequest}`
answer object its broadcast writes, the completion as a tool call when
it is a tool name from `tools`
followed by JSON arguments, else as text; beside the OpenAI chat request
it answers this counts as one format.
- Vercel AI SDK and Mastra: formats the legacy `ai.*` telemetry, the
provider prompt in its v1 and v2 part spellings, the parent span's
`ai.prompt`, and the answer split across
`ai.response.text` and `ai.response.toolCalls`, with images given as a
URL, base64 or a serialised Buffer.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/nerve-pod/issues/266

<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
  Seventh of the stack, on top of #13115.
<!--Please delete paragraphs that you did not use before submitting.-->
2026-10-15 19:51:52 +00:00
Nityananda Gohain
1dbc9ed47c feat(tracedetail): format LangChain serialised runs (#13137)
<!--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
- Formats the LangSmith OTel exporter's serialised run inputs and
outputs, `{messages: [[…]]}` and `{generations: [[…]]}` of
langchain_core `{lc, type, id, kwargs}` messages: roles from the message
class, string content and langchain_core's standard content blocks, tool
calls and tool messages,
  and `error` for a failed call whose body sits in `response_metadata`.
- The finish reason is read under the key each partner writes:
`finish_reason` for most, `stop_reason` for anthropic, `stopReason` for
bedrock, `done_reason` for ollama. Provider-native thinking blocks are
kept as sent for now.


<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/nerve-pod/issues/266


<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
  Sixth of the stack, on top of #13115.
2026-10-15 19:51:42 +00:00
Vikrant Gupta
65ad9b5f38 feat(statsreporter): open the stats endpoint to every authenticated caller (#13152)
#### Description

- `GET /api/v1/stats` drops the viewer role gate and moves to open
access. Authentication still applies; the handler reads the organization
from the caller's claims, so the response stays scoped to their org.
2026-10-15 19:44:15 +00:00
Nityananda Gohain
de1912de43 feat(tracedetail): format OpenAI Responses bodies (#13136)
<!--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
- Formats raw OpenAI Responses bodies, a request `{input, instructions}`
or a response `{output, status}` of items: messages, function and custom
tool calls with their outputs, reasoning summaries, compaction, and
every built-in tool item as a server tool call or its response.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/nerve-pod/issues/266

<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
Fifth of the stack, on top of #13115. No capture reaches this converter
directly, OpenInference's flattened messages cover the Agents SDK; it
serves gateways recording Responses bodies and the fallback path.

<!--Please delete paragraphs that you did not use before submitting.-->
2026-10-15 18:46:51 +00:00
Nityananda Gohain
81ff25b2f8 feat(tracedetail): format openllmetry legacy prompt and completion keys (#13135)
<!--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
- Formats the flattened `gen_ai.prompt.N.*` and `gen_ai.completion.N.*`
keys openllmetry's instrumentations wrote before their semconv rewrite,
still written by mistralai and by crewai through the openai
instrumentation 0.45.6.
- A semconv output message with no parts takes its tool call from the
`gen_ai.completion.N` keys beside it, which litellm writes while its
semconv message never carries the call.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/nerve-pod/issues/266

<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
  Fourth of the stack, on top of #13115.

<!--Please delete paragraphs that you did not use before submitting.-->
2026-10-15 18:30:03 +00:00
Nityananda Gohain
923d758f7c feat(tracedetail): format OpenInference flattened messages (#13133)
<!--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
- Formats the flattened `llm.input_messages.N.*` and
`llm.output_messages.N.*` keys OpenInference writes on LLM spans. They
take precedence over the raw provider request the mapper copies, since
they have one shape across providers; a side without them falls back to
the raw copy.
- Handles text, media and reasoning items, tool calls and results, the
legacy `function_call` keys and `llm.finish_reason`; runs of assistant
messages fold into one.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/nerve-pod/issues/266


<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
Third of the stack, on top of #13115. Thread integration suite passes
end to end for all three.
2026-10-15 18:29:26 +00:00
Nityananda Gohain
eeb57ceff2 feat(tracedetail): format gen_ai system instructions and finish reasons (#13131)
<!--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
- `gen_ai.system_instructions` leads the formatted input as a system
message; `gen_ai.response.finish_reasons` sets the finish reason of each
output message over whatever the payload carried, whichever converter
ran.
- Semconv repairs for pydantic-ai: tool call arguments given as a JSON
string are decoded, `result` on a tool call response becomes `response`.
Finish reasons inside semconv messages go through the alias table,
litellm writes `tool_calls`.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/nerve-pod/issues/266

<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
  Second of the stack, on top of #13115.

<!--Please delete paragraphs that you did not use before submitting.-->
2026-10-15 18:28:35 +00:00
Ashwin Bhatkal
d28cd7cf2e ci(e2e): run e2e on every PR that changes the frontend (#13147)
#### Description

- e2e now runs on every PR that changes `frontend/` or `tests/e2e/`. The
`safe-to-e2e` label still opts in any other PR.
- A new `changes` job reads the PR's file list and gates the `fmtlint`
and `test` jobs on it.
- A new `e2e` job is the single check to mark as required. It fails when
e2e was needed but did not pass, including fork and dependabot PRs still
waiting on `safe-to-test`.
- In the merge queue, `e2e` passes without running the suite, so the
queue is not blocked.
- New pushes cancel the PR's in-flight run.
2026-10-15 18:26:10 +00:00
Vinicius Lourenço
268c67d34b test(e2e): add e2e for routing (#13078)
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description

This is a PR to add basic testing for our routing, specific parts of the
app that goes from one page to another.

This will act as a base test layer for upcoming react-router upgrade,
this PR also follows the new approach of drivers instead of random
getByTestId in the spec layer.

I also took the opportunity to add support to run the E2E with the base
path feature (eg: /signoz/).

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR

Closes https://github.com/SigNoz/uforge-pod/issues/6190
2026-10-15 17:53:54 +00:00
Gaurav Tewari
206349b75c fix: hide create alert in case of ai query (#13122)
#### Description

AI query do not support creating alert for now. so we should remove
create alert action as well for ai query.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR

https://github.com/SigNoz/nerve-pod/issues/460 


#### Screenshots / Screen Recordings


https://github.com/user-attachments/assets/d2634f0e-1334-47b3-9ad0-1cb3a226d81b



<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information

<!--Please delete paragraphs that you did not use before submitting.-->

---------

Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
2026-10-15 16:55:44 +00:00
Manika Malhotra
11ae922ff7 feat(components): adopt @signozhq/ui 0.2.0 across the app (#12971)
<!--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

Bumps @signozhq/ui 0.1.0 to 0.2.0 and @signozhq/design-tokens 2.1.6 to
2.2.1.

Reworked components (breaking):

- Button: `variant` and `color` are a required pair; `outlined`, `ghost`
and `dashed` only take `secondary`. `destructive` is now `danger`;
`none`, the `action` variant, the `icon` size and `background` are gone.
Icon-only buttons use `icon` plus `aria-label`. `className` and `style`
are no longer accepted, so the local overrides went with them. Adds
`width`, `maxWidth`, `textOverflow` and `loadingTooltip`.
- ButtonGroup: moved to `@signozhq/ui/button-group`, driven by `items`.
- Badge: `solid`/`outlined` variants and a semantic palette (`primary`,
`secondary`, `danger`, `warning`, `success`, `info`, `archive`,
`highlight-danger`) replace the hue names (`robin`, `cherry`,
`vanilla`...). `capitalize` and `textEllipsis` became `textTransform`
and `textOverflow`. `closable` is gone; removable chips use the new
`Pill.Closeable`.
- Tooltip: Radix to Base UI. `TooltipSimple`, `TooltipRoot`,
`TooltipTrigger` and `TooltipContent` collapse into one `Tooltip` with
`title`. Per-tree `TooltipProvider` wrappers are no longer needed (44 to
1).
- DropdownMenu: replaced by `Dropdown` (Base UI Menu), built from typed
`items` (action, link, checkbox, radio group, submenu, group) instead of
composed `DropdownMenu*` parts and `MenuItem`.
- ToggleGroup, RadioGroup, Tabs: Radix to Base UI, each driven by an
`items` prop instead of `*Item`/`*Trigger` children. `TabItemProps` is
now `TabsItemProps`.
- Checkbox, Switch: Radix to Base UI, semantic `color` required. Switch
requires `textPlacement`.

Contracts shared by the reworked components, enforced by their types:

- `disabled` needs a `disabledTooltip` (and `readOnly` a
`readOnlyTooltip`), so every disabled control now says why (about 140
reasons added).
- `data-testid` is written as `testId`.

App side:

- About 3,300 lines of SCSS overrides removed, since the components no
longer take `className` and their defaults now match the designs.
- AuthZButton builds on Button's `disabledTooltip`/`loadingTooltip`
instead of wrapping it in AuthZTooltip; new AuthZDropdown gates Dropdown
items the same way.
- Jest gets a `PointerEvent` polyfill for Base UI; `styles.scss` sets
compatibility variables for badge width, dropdown height, toggle-group
label width and dropdown z-index.

#### How to review the code

This PR got a lot larger than I wanted so I tried few ways to help you
review/read all these changes.

First, pick the commits that belongs to your pod/area first.

For pulse-pod:

-
da5cacec54
-
dc85d92b3c
-
9e5eae6169
-
6c8b1d5282

For events pod:

-
92c74f6067
-
74894b372a
-
45d1646abf

And the rest of the commits, well, I will appreciate if you spent some
time looking at them since they don't have specific owner for now (by
CODEOWNERS), and few of them I maintain (Settings/AuthZ for eg).

#### How to review visually

To be able to review the changes visually, I build a tool called
[sbshot](https://github.com/SigNoz/sbshot/) that help us capture the
stories before/after and then generate a website to let you visualize
the diff and know by % how much each page changed in pixels.

You can take a look at this tool here:
https://pr-docs.viniciusl.com.br/signoz-v3-update-components-1012

I tried to add as many tags as possible to let you filter, so my
suggestion for you is:

- take a look first at the stories that matter for you
- then read the code of the changes, to ensure everything was covered
- and finally, after you think it's good enough, run the app 

<!--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/6157

---------

Co-authored-by: Vinícius Lourenço <vinicius@signoz.io>
2026-10-15 13:31:58 +00:00
Nityananda Gohain
3e087fe7bd feat: return formatted gen ai message in waterfall (#13121)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
<!--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
- Waterfall spans carry `formattedInput`, `formattedOutput`, `formatter`
and `formatterWarnings`, same as thread spans, so span details needs no
separate API. Set only for spans with gen_ai messages, omitted
otherwise.
- Waterfall queries read the JSON attributes column when
`use_trace_attributes_json` is on; org id threaded through for the flag.
- Thread endpoint: the same four keys renamed from snake_case to
camelCase, in line with the rest of the API.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
 Closes SigNoz/nerve-pod#268

<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
- Breaking for the thread API on `main`: `formatted_input`,
`formatted_output`, `formatter_warnings` are now `formattedInput`,
`formattedOutput`, `formatterWarnings`.
- Formatted fields come from the JSON column only; the waterfall
`attributes` map still reads the legacy maps (#12798).
2026-10-15 12:33:57 +00:00
Abhi kumar
9ec39f2494 feat(query-builder): field placeholders, default row limit and synced fields (#13113)
#### Description

- Query builder field rules gain a `default` state whose placeholder
names the value an empty field falls back to. Every field input reads it
(group by, having, order by, limit, legend format, step interval);
built-in placeholders now live on the `ADD_ONS` entries.
- Panel kinds can declare `defaultRowLimit`: sent as `limit` only for a
single builder query that sets none, since per-query limits across
several queries can keep different groups and break their join.
- Panel kinds can declare `syncedQueryBuilderFields`: an edit to one
query copies to the rest, and a newly added query takes the existing
value. Values that already differ are left alone until edited.
- Add-on rows share one height, label width, bordered collapse button
and placeholder colour; the query pane keeps a stable scrollbar gutter.

#### Additional Information

- No panel kind uses `defaultRowLimit` or `syncedQueryBuilderFields`
yet; the Scatter Plot panel (#13051) is the first.
- Order By gains a placeholder ("Select fields to order by");
`GroupByFilter`, `OrderByFilter` and `HavingFilter` take an optional
`placeholder`, and other callers keep their current text.

Closes https://github.com/SigNoz/pulse-pod/issues/578
2026-10-15 11:43:28 +00:00
Nityananda Gohain
ef67a47cf3 feat: update ai attribute mapping (#13120)
<!--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
- Claude Code cache tokens never reached pricing: its bare keys
(`input_tokens`, `cache_read_tokens`, `cache_creation_tokens`,
`tool_name`) are now sources.
- Also mapped: Cursor hooks keys, vLLM's response-only model, gateway
cache and reasoning token spellings, old OpenLLMetry finish reasons,
CrewAI agent name and id.
- `gen_ai.operation.name` mapped again from span kind keys; safe since
the collector no longer overwrites native values.
- llm condition gains `llm.input_messages` so failed OpenInference
calls, which carry no model key, still map.
- README next to the definitions records source provenance and what is
left unmapped.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Closes https://github.com/SigNoz/nerve-pod/issues/408

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2026-10-15 11:41:05 +00:00
Nityananda Gohain
441f7e2927 feat: thread rows by the gen_ai gate, formatted tool spans (#13115)
<!--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
- Thread rows are the explorer gate: spans with a model, tool or agent
name. Agent spans come back with empty formatted lists; Basically
matching the filter to be same as in other places.
- `FormatMessages` takes the span's JSON attribute document instead of
the two message values, so a converter can read any attribute and object
values arrive whole. The key walk moves out of spantypes.
- Tool spans are formatted: `gen_ai.tool.call.arguments` as an assistant
`tool_call`, `gen_ai.tool.call.result` as a tool `tool_call_response`,
with the LangChain callback and traceloop decorator envelopes unwrapped.


<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/nerve-pod/issues/266

<!--Anything reviewers should keep in mind while reviewing -->
#### Additional Information
First of a stack. Each following PR adds one message converter and only
the shared code it needs.
2026-10-15 11:30:02 +00:00
Vikrant Gupta
4aff26f696 docs: require a copy control for resource selectors in the authz guide (#13140)
## Description

- Adds core rule 8 to `frontend/docs/authz-guide.md`: every resource a
role can be scoped to must show its selector with a copy control, so
admins can paste it into the role editor instead of digging IDs out of
URLs or API responses.
- Adds the matching item to the migration checklist and points at
`permissions.config.ts` for the per-resource selector format.
2026-10-15 11:21:13 +00:00
Aditya Singh
47a773245b feat(trace-details): move the span filter and span actions off v3 query APIs (#13134)
#### Description

- The trace details waterfall filter now runs on `query_range` v5. The
typed expression goes to the backend as `filter.expression`, scoped to
the trace id. Before, the expression was converted to filter items and
v5 converted them back, so an OR or a function produced no request.
Those expressions now run.
- The Highlight errors switch and the category buttons read their state
from the committed expression.
- The span details actions (Filter for, Filter out, Group by) build the
filter key locally instead of fetching v3 `attribute_keys`. The stored
key has an empty data type and context, the same as a key typed into the
query builder. The explorer request is unchanged.
- Removed the unused `useTraceActions` hook.

#### Issues closed by this PR

Closes https://github.com/SigNoz/events-pod/issues/159

#### Additional Information

- Group-by from the span panel now sends an empty `fieldDataType`
instead of `string`. The backend resolves the key by name.
- The waterfall and flamegraph fetches (`/api/v4/traces/{id}/waterfall`,
`/api/v3/traces/{id}/flamegraph`) are feature routes and are not part of
this change.
- The filter row commits only expressions that parse. The Highlight
errors switch now reads quoted or uppercase `true` as on and `!=` as
off, which main got wrong.
- Part of https://github.com/SigNoz/events-pod/issues/156.
2026-10-15 10:12:50 +00:00
Aditya Singh
375b3f8305 feat(query-builder): move group-by and order-by key suggestions to the v5 fields API (#13127)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
#### Description

- The group-by and order-by filters in the query builder still fetched
key suggestions from the v3 `attribute_keys` API. Every explorer, panel
editor and alert form that is otherwise on v5 called it through them.
- Both filters now read `GET /api/v1/fields/keys` through the existing
`useFieldKeysSuggestion` hook. The dropdown rows, badges, chips and the
stored group-by shape are unchanged.
- `container/ExplorerOrderBy` had no importer and is removed.

#### Issues closed by this PR

Closes https://github.com/SigNoz/events-pod/issues/158
Part of https://github.com/SigNoz/events-pod/issues/156


### ScreenRecordings



https://github.com/user-attachments/assets/fa907fa3-e6f0-45b6-b171-82e212ccda29



https://github.com/user-attachments/assets/0f6f246a-786e-42c2-b528-4949785ca7e1



https://github.com/user-attachments/assets/933d7495-23af-4f2f-8aca-07693149f85a





#### Additional Information

- A new group-by pick stores `type` from the v5 field context
(`attribute` / `resource`). v3 wrote `tag`. Saved dashboards, alerts and
views are not rewritten.
2026-10-14 19:52:18 +00:00
Aditya Singh
aaec7c89c5 feat(saved-views): hide the old saved views ui behind the flag (#13028)
#### Description
- with the saved views flag on, the old views ui is hidden.. bottom
explorer bar on logs / traces / metrics / meter, the Views tabs on those
pages and the All Views link on the home widget. views are in the
explorer header and panel now.
- views shortcuts and the go to views palette entries removed, not
behind the flag since the flag turns on next.

#### Issues closed by this PR

Part of https://github.com/SigNoz/events-pod/issues/71

#### Additional Information
- meter never had create alert / add to dashboard in its bar (hidden for
meter), so hiding it there loses nothing.. the bottom bar commit note
saying otherwise is wrong.
- old views urls still open the page with an empty pane when the flag is
on.. routes go away in the cleanup PR.
- flag off is same as main apart from the shortcuts.
2026-10-14 13:21:17 +00:00
Aditya Singh
57c193ec8f feat(saved-views): all views panel in the quick filters sidebar (#13022)
#### Description
- real all views panel in the sidebar.. views split into created by me /
created by others, search by name, `↺` on the open view to clear it.
empty state has a create view button, same modal as the header.
- row `...` menu with copy link, edit view details and delete. copy link
copies the whole explorer link, a link with only the viewKey does not
open the view (same in the v1 bar).
- hover card on a view like the trace waterfall one.. filter per query
with items and expression combined, coloured with the query search
theme, metric line for metrics and meter, last updated.
`@lezer/highlight` added for the colouring, same version already in the
tree.
- explorer columns for a view now come from the view itself, not the
views list.. if the list call failed the explorer fell back to default
columns and the header went amber with no way to discard. header shows a
red failed to load state when the view itself does not load.
- unsaved changes check maps the stored view through the same mapper as
the page.. older views kept fields we never write (`reduceTo` on a
graph) and were always amber.
- qf settings drawer matched to the panel, sidebar tooltips wait 500ms,
meter explorer gets saved views, metrics explorer tab loses the extra
padding around the sidebar.

#### Issues closed by this PR

Part of https://github.com/SigNoz/events-pod/issues/71

#### Screen recording



https://github.com/user-attachments/assets/f882b5b7-c108-49f3-b62e-132b0db3a60f



#### Additional Information
- behind `SAVED_VIEW_ENABLED`, flag off is today's ui.
- make default, duplicate and add to shortcuts not in this PR.. default
needs a backend api.
- qf settings drawer is a shared component, every qf page gets the new
header and search.
- browser pass on logs / traces / metrics / meter in progress.
2026-10-14 13:20:20 +00:00
1342 changed files with 34639 additions and 21388 deletions

View File

@@ -0,0 +1,164 @@
---
name: sbshot-visual-diff
description: Shoot Storybook stories with sbshot at two git refs and produce a diff a reviewer can read. Use when asked for a visual diff, before/after screenshots, a screenshot comparison for a PR, or to prove a change did not alter screens it should not have.
---
# sbshot visual diffs
`npx -y github:SigNoz/sbshot` shoots Storybook stories and pixel-diffs two runs.
Use it. Do not hand-roll a Playwright screenshot loop or a canvas pixel-compare:
sbshot already handles story selection, themes, settling, retries, diffing,
tagging, a viewer and a static export, and its runs are the format the rest of
the team can open.
sbshot is not installed by any package. It is run through `npx`, so nothing it
knows travels into this repo on its own. This file is where that knowledge
lives.
## The shape of a run
```
capture <storybook> --name-run <ref> # once per git ref
diff <baseline> <after> # pairs them by relative path
tag <diff> <tag> <story-id>... # what the reviewer should look at
export <diff> --out <dir> # a static site, hostable
```
`<storybook>` is a running dev server (`http://localhost:6006`), a build
directory holding `index.json`, or a project directory with `.storybook/`. The
frontend has **no `build-storybook` script**, only `storybook dev -p 6006`, so
point `capture` at a dev server.
## Cover the change with stories first
**A diff can only show what a story renders.** A screen, state or variant with no
story is invisible to sbshot: it produces no pair, so it reads as "nothing
changed" rather than "never looked at". This is the failure mode that makes a
clean-looking diff worthless.
Before capturing, list the screens and states the change touches and check each
has a story. For anything missing, write it first — the `signoz-page-story`
skill covers how. New states reached only by an interaction need a `play`
function, or the capture shoots the page at rest and the state never appears.
Then confirm it worked: the two runs' story counts should differ by exactly the
stories the change adds. A new screen that produced no new story id is a screen
nobody is reviewing. Say the count in the summary, so a reader can tell coverage
from silence.
## Rules that are not optional
**One freshly started Storybook per ref.** Never start one server and switch git
branches under it. Vite's HMR reloads the page, but some modules do not
re-initialise, so the shot differs silently from a clean boot and the difference
looks like a real change. Settle time does not fix this and is not the variable.
Use one worktree and one port per ref, and stop each server before the next.
**Capture both themes: `--theme dark,light`.** Without it sbshot shoots the
story's own default and everything lands in a single `default/` folder, so every
regression in the other theme is invisible. Themes double the shot count, so
scope with `--stories` or `--title`, never by dropping a theme.
**Baseline is the merge-base, not `origin/main`.** `git merge-base origin/main
<branch>`. Against a moved main, every commit main gained since the branch's base
shows up reversed, as though the branch reverted it.
**The percentage sorts the list, it does not tell you what matters.** A dialog
that dims the page scores high for a one-line change, and a whole new form
section can score low. Open the shots and say what changed on each screen. The
biggest number is usually the least interesting one.
**If you cannot explain a pair, assume the capture is at fault, not the code.**
Open it, crop both sides and compare them before calling it noise. A pixel count
and a good guess are not evidence.
## Noise, and the floor that proves it
The same story does not always render identically twice. Pairs often move for
reasons that have nothing to do with the change:
- a focus ring left on whatever a `play` function clicked last
- a scrollbar appearing, or its thumb resting a few pixels off
- a toast, tooltip or dialog caught mid-fade
- a text caret blinking, or a selection highlight
- an animation or spinner at a different frame
- relative timestamps ("2 days ago") crossing a boundary
- hover or active styling from where the pointer happened to rest
- a late-loading font or image shifting a line of text
Do not argue about these one at a time. **Measure them**: shoot the same commit
twice and diff those two runs against each other. Anything that moves there moved
for no reason, so it is noise.
```
capture <storybook> --name-run noise-a
capture <storybook> --name-run noise-b # same tree, its own fresh server
diff noise-a noise-b --name-diff noise
diff <baseline> <after> --noise noise # marks the movers flaky
```
**The noise run has to cover the same stories as the real diff.** Do not scope it
down to the few pairs that moved, however tempting that is. `--noise` marks a
pair flaky when the noise diff does not show it as unchanged, so every story the
noise run leaves out is marked flaky too, including the new screens the reviewer
is there to look at. Shooting two narrow runs and diffing those against each
other has the same problem. Either shoot the full set twice, or skip `--noise`
and explain the few movers by hand.
That is the trade: a second full capture costs about as long as the first, so on
a small diff it is often quicker to crop the two or three movers and say what
each one is. Use `--noise` when the diff is large, or when wiring `--fail-over`
into CI, where something has to decide without a person looking.
If a pair is flaky every run and the state it captures matters, fix the story
rather than living with it: blur what the `play` clicked, freeze the clock, or
assert the state instead of shooting it.
## Making it readable for a reviewer
A reviewer cannot open `localhost`, so `sbshot ui` is for you, not for them.
1. `tag` the pairs that matter, with `--note` saying why. The viewer filters by
tag, so the reviewer lands on the real changes instead of scrolling past
noise that scored higher.
2. `export <diff> --out <dir>`. It writes a static site carrying only the pairs
that are not identical, converted to WebP, so a large diff stays small.
3. Host it. A Claude artifact works: publish the export's files and give the
reviewer the link. `index.html` is reserved for the artifact's own page, so
publish sbshot's entry as `viewer.html` and link to
`viewer.html#/job/diff/<diff>`.
4. Put the link in the PR body under **Screenshots / Screen Recordings**.
Say the numbers plainly: how many screens changed, how many are new, how many are
pixel-identical, how many are flaky. The count of unchanged screens is often the
most useful claim in the diff, because it is the one a reviewer cannot check by
eye.
## Operational traps
- A full capture is hundreds of shots. Scope with `--title "Pages/Alerts"` or
`--stories "a,b,c"` (substring match on id or Title/Name, repeatable).
- A fresh worktree cannot build Storybook: `i18n-translations-hash.json` is
gitignored and generated. Run `node ./i18-generate-hash.cjs` in
`<worktree>/frontend`, and symlink `node_modules` from the main checkout.
- `pnpm test:storybook` starts its own server on 6006 and hangs silently if
something already holds that port. Free it first.
- ImageMagick on macOS may have no default font, so `--mode green-parallel` fails
with ``unable to read font `' ``. Map Helvetica to a real file in
`~/.config/ImageMagick/type.xml`.
- `sbshot link <dir> <name>` adds a run from elsewhere into the workspace, but it
symlinks. Keep the source directory alive until you have exported.
- The export directory is build output. Delete it when done, or gitignore it.
- Running the whole Storybook test suite locally fails broadly with
`page.goto: Timeout` from resource starvation. Scope it; CI is the real signal.
## Done means
- [ ] every screen and state the change touches has a story, written before capturing
- [ ] one capture per ref, each from its own freshly started Storybook
- [ ] `--theme dark,light` on every capture
- [ ] baseline is the merge-base
- [ ] noise either measured with a full second capture, or each mover explained by hand
- [ ] every non-flaky pair opened and explained, not inferred from its size
- [ ] the pairs that matter tagged, with a note
- [ ] exported and hosted, and the link in the PR body

View File

@@ -151,9 +151,42 @@ that only exists on a trial needs the plan too:
`args: { plan: 'on-trial', revoked: ['create:subscription'] }`. A permission
whose denial changes nothing on screen gets no story: say so in the PR.
Verify these by their disabled states, not their text. The page reads the same
either way, so a story that is wrong looks right: read `disabled` off the buttons
the permission gates, and check the denial callout is there or gone.
Check these by what is turned off, not by what the page says. The page reads the
same either way, so a story that is wrong still looks right. Check that the
control the permission guards is off, and that the denial message is there or
gone.
**Do not use `toBeEnabled` or `toBeDisabled`.** They only look at the `disabled`
attribute. `@signozhq/ui` turns a button off with `aria-disabled` instead, so
these matchers pass on a button nobody can click, and the test goes green while
clicking straight through a denial. Check the attribute the component really
sets:
```ts
expect(save).toHaveAttribute('aria-disabled', 'true');
```
**Some controls are off with no attribute at all.** The browser turns off
everything inside a disabled `<fieldset>` without marking it, and a switch is a
`<button>` underneath. For those, an attribute check proves nothing, and so does
looking for a parent `fieldset[disabled]`. Click it and check nothing happened:
```ts
const before = toggle.getAttribute('aria-checked');
await userEvent.click(toggle);
await expect(toggle.getAttribute('aria-checked')).toBe(before);
```
**Check that a new assertion can fail.** Flip it once and run it. If that story
goes red and nothing else does, the assertion is real; put it back. A test that
cannot fail is worse than no test, because it looks like coverage.
**Delete a story once its difference disappears.** Say a story checks that a
button reads "View" rather than "Edit" when a permission is missing. If the label
later becomes the same either way, that story passes no matter which permissions
are held, including all of them, and it is testing nothing. When a behaviour
stops depending on a permission, delete the story or rewrite it to lock in the
new behaviour, and say which one you did in the PR.
## Rules

View File

@@ -63,6 +63,10 @@ not catch an unstyled page. Take them with
[sbshot](https://github.com/SigNoz/sbshot), e.g.
`sbshot capture http://localhost:6006 --title <story title> --theme dark,light`.
That is one run, for checking the story you just wrote. Comparing two branches is
a different job: see the `sbshot-visual-diff` skill, which covers the baseline to
pick, measuring the noise and exporting something a reviewer can open.
## Gates
- **Console silent.** `[storybook] no msw handler`, a 501 from the catch-all, an

1
.gitattributes vendored
View File

@@ -1,3 +1,4 @@
*.css linguist-detectable=false
*.stories.mocks.tsx linguist-generated=true
**/__story_mockdata__/** linguist-generated=true
tests/e2e/drivers/** linguist-generated=true

View File

@@ -2,17 +2,37 @@ name: e2eci
on:
pull_request:
types:
- labeled
pull_request_target:
types:
- labeled
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest
outputs:
e2e: ${{ steps.changes.outputs.e2e }}
steps:
- name: changes
id: changes
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
FORCE: ${{ contains(github.event.pull_request.labels.*.name, 'safe-to-e2e') }}
run: |
CHANGED_FILES=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename | select(startswith("tests/integration/") | not)')
PATTERN='^(frontend/|tests/|\.github/workflows/e2eci\.yaml$|cmd/enterprise/Dockerfile\.with-web\.integration$)'
if [ "$FORCE" = "true" ]; then
echo "e2e=true" >> "$GITHUB_OUTPUT"
elif printf '%s\n' "$CHANGED_FILES" | grep -qE "$PATTERN"; then
echo "e2e=true" >> "$GITHUB_OUTPUT"
else
echo "e2e=false" >> "$GITHUB_OUTPUT"
fi
fmtlint:
if: |
((github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && contains(github.event.pull_request.labels.*.name, 'safe-to-e2e')
needs: changes
if: needs.changes.outputs.e2e == 'true'
runs-on: ubuntu-latest
steps:
- name: checkout
@@ -41,9 +61,8 @@ jobs:
project:
- chromium
shard: [1, 2, 3, 4]
if: |
((github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && contains(github.event.pull_request.labels.*.name, 'safe-to-e2e')
needs: changes
if: needs.changes.outputs.e2e == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
@@ -152,3 +171,14 @@ jobs:
name: playwright-artifacts-${{ matrix.project }}-${{ matrix.shard }}
path: tests/e2e/artifacts/
retention-days: 5
# Required status check. Fails when e2e was needed but did not pass.
e2e:
if: always()
needs: [changes, fmtlint, test]
runs-on: ubuntu-latest
steps:
- name: verdict
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
echo "::error::e2e did not pass."
exit 1

View File

@@ -93,17 +93,19 @@ func runGenerateAuthz(_ context.Context) error {
registry := coretypes.NewRegistry()
allowedResources := map[string]bool{
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceFactorAPIKey).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceDashboard).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceLicense).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceSubscription).String(): true,
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceLogs).String(): true,
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceTraces).String(): true,
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMetrics).String(): true,
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMeterMetrics).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceQuickFilter).String(): true,
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceFactorAPIKey).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceDashboard).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceLicense).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceSubscription).String(): true,
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceLogs).String(): true,
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceTraces).String(): true,
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMetrics).String(): true,
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMeterMetrics).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceQuickFilter).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceSavedView).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceNotificationChannel).String(): true,
}
allowedTypes := map[string]bool{}

View File

@@ -251,6 +251,7 @@ components:
opsgenie: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelOpsgenieConfig'
pagerduty: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelPagerdutyConfig'
slack: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelSlackConfig'
telegram: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfig'
webhook: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelWebhookConfig'
propertyName: kind
oneOf:
@@ -264,6 +265,7 @@ components:
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJiraConfig'
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJSMOpsConfig'
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelIncidentIOConfig'
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfig'
type: object
AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelEmailConfig:
properties:
@@ -373,6 +375,18 @@ components:
- kind
- spec
type: object
AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfig:
properties:
kind:
enum:
- telegram
type: string
spec:
$ref: '#/components/schemas/AlertmanagertypesChannelTelegramConfig'
required:
- kind
- spec
type: object
AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelWebhookConfig:
properties:
kind:
@@ -525,6 +539,7 @@ components:
- jira
- jsmops
- incidentio
- telegram
type: string
AlertmanagertypesChannelListOrder:
enum:
@@ -729,6 +744,31 @@ components:
- title
- value
type: object
AlertmanagertypesChannelTelegramConfig:
properties:
apiUrl:
type: string
botToken:
format: password
type: string
chatId:
format: int64
type: integer
disableNotifications:
type: boolean
message:
type: string
messageThreadId:
type: integer
sendResolved:
nullable: true
type: boolean
title:
type: string
required:
- botToken
- chatId
type: object
AlertmanagertypesChannelWebhookConfig:
properties:
bearerToken:
@@ -1022,6 +1062,8 @@ components:
- jsmops_configs
- required:
- incidentio_configs
- required:
- telegram_configs
- required:
- discord_configs
- required:
@@ -1042,8 +1084,6 @@ components:
- victorops_configs
- required:
- sns_configs
- required:
- telegram_configs
- required:
- webex_configs
- required:
@@ -1119,7 +1159,7 @@ components:
type: array
telegram_configs:
items:
$ref: '#/components/schemas/ConfigTelegramConfig'
$ref: '#/components/schemas/AlertmanagertypesTelegramReceiverConfig'
type: array
victorops_configs:
items:
@@ -1263,7 +1303,7 @@ components:
type: array
telegram_configs:
items:
$ref: '#/components/schemas/ConfigTelegramConfig'
$ref: '#/components/schemas/AlertmanagertypesTelegramReceiverConfig'
type: array
victorops_configs:
items:
@@ -1334,6 +1374,34 @@ components:
- timezone
- startTime
type: object
AlertmanagertypesTelegramReceiverConfig:
properties:
api_url:
$ref: '#/components/schemas/ConfigURLType2'
chat:
format: int64
type: integer
chat_file:
type: string
disable_notifications:
type: boolean
http_config:
$ref: '#/components/schemas/ConfigHTTPClientConfig'
message:
type: string
message_thread_id:
type: integer
parse_mode:
type: string
send_resolved:
type: boolean
title:
type: string
token:
type: string
token_file:
type: string
type: object
AlertmanagertypesTestableNotificationChannel:
properties:
config:
@@ -1374,9 +1442,13 @@ components:
AuthtypesAuthDomainConfigGoogle:
properties:
kind:
$ref: '#/components/schemas/AuthtypesAuthNProvider'
allOf:
- $ref: '#/components/schemas/AuthtypesAuthNProvider'
description: The kind of authn provider.
spec:
$ref: '#/components/schemas/AuthtypesGoogleConfig'
allOf:
- $ref: '#/components/schemas/AuthtypesGoogleConfig'
description: The google auth configuration.
required:
- kind
- spec
@@ -1384,9 +1456,13 @@ components:
AuthtypesAuthDomainConfigOIDC:
properties:
kind:
$ref: '#/components/schemas/AuthtypesAuthNProvider'
allOf:
- $ref: '#/components/schemas/AuthtypesAuthNProvider'
description: The kind of authn provider.
spec:
$ref: '#/components/schemas/AuthtypesOIDCConfig'
allOf:
- $ref: '#/components/schemas/AuthtypesOIDCConfig'
description: The oidc configuration.
required:
- kind
- spec
@@ -1394,9 +1470,13 @@ components:
AuthtypesAuthDomainConfigSAML:
properties:
kind:
$ref: '#/components/schemas/AuthtypesAuthNProvider'
allOf:
- $ref: '#/components/schemas/AuthtypesAuthNProvider'
description: The kind of authn provider.
spec:
$ref: '#/components/schemas/AuthtypesSamlConfig'
allOf:
- $ref: '#/components/schemas/AuthtypesSamlConfig'
description: The saml configuration.
required:
- kind
- spec
@@ -2159,8 +2239,6 @@ components:
type: object
CloudintegrationtypesGCPAccountConfig:
properties:
connectionMethod:
$ref: '#/components/schemas/CloudintegrationtypesGCPConnectionMethod'
deploymentProjectId:
type: string
deploymentRegion:
@@ -2170,83 +2248,13 @@ components:
type: string
type: array
required:
- connectionMethod
- deploymentProjectId
- deploymentRegion
- projectIds
type: object
CloudintegrationtypesGCPConnectionArtifact:
properties:
cliCommand:
type: string
createSecretsCommand:
type: string
createServiceAccountCommand:
type: string
required:
- cliCommand
- createSecretsCommand
- createServiceAccountCommand
type: object
CloudintegrationtypesGCPConnectionMethod:
enum:
- manual
- agent
type: string
CloudintegrationtypesGCPIntegrationConfig:
properties:
connectionMethod:
$ref: '#/components/schemas/CloudintegrationtypesGCPConnectionMethod'
deploymentProjectId:
type: string
deploymentRegion:
type: string
projectIds:
items:
type: string
type: array
telemetryCollectionStrategy:
$ref: '#/components/schemas/CloudintegrationtypesGCPTelemetryCollectionStrategy'
required:
- connectionMethod
- deploymentProjectId
- deploymentRegion
- projectIds
- telemetryCollectionStrategy
type: object
CloudintegrationtypesGCPLogsCollectionStrategy:
properties:
sinkFilter:
type: string
required:
- sinkFilter
type: object
CloudintegrationtypesGCPMetricsCollectionStrategy:
properties:
metricNames:
items:
type: string
type: array
required:
- metricNames
type: object
CloudintegrationtypesGCPPostableAccountConfig:
properties:
connectionMethod:
$ref: '#/components/schemas/CloudintegrationtypesGCPConnectionMethod'
deploymentProjectId:
type: string
deploymentRegion:
type: string
projectIds:
items:
type: string
type: array
required:
- connectionMethod
- deploymentProjectId
- deploymentRegion
- projectIds
type: object
CloudintegrationtypesGCPServiceConfig:
properties:
@@ -2269,13 +2277,6 @@ components:
required:
- enabled
type: object
CloudintegrationtypesGCPTelemetryCollectionStrategy:
properties:
logs:
$ref: '#/components/schemas/CloudintegrationtypesGCPLogsCollectionStrategy'
metrics:
$ref: '#/components/schemas/CloudintegrationtypesGCPMetricsCollectionStrategy'
type: object
CloudintegrationtypesGettableAccountWithConnectionArtifact:
properties:
connectionArtifact:
@@ -2412,7 +2413,7 @@ components:
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureAccountConfig'
gcp:
$ref: '#/components/schemas/CloudintegrationtypesGCPPostableAccountConfig'
$ref: '#/components/schemas/CloudintegrationtypesGCPAccountConfig'
type: object
CloudintegrationtypesPostableAgentCheckIn:
properties:
@@ -2647,6 +2648,7 @@ components:
projectIds:
items:
type: string
nullable: true
type: array
required:
- deploymentProjectId
@@ -8124,6 +8126,31 @@ components:
- metric
- value
type: object
PromotetypesIndexMaterializedPathsResult:
properties:
indexed:
items:
$ref: '#/components/schemas/PromotetypesPromotePath'
nullable: true
type: array
skipped:
items:
$ref: '#/components/schemas/PromotetypesPromotePath'
nullable: true
type: array
required:
- indexed
- skipped
type: object
PromotetypesPostableIndexMaterializedPaths:
properties:
dryRun:
type: boolean
signal:
type: string
required:
- signal
type: object
PromotetypesPromotePath:
properties:
context:
@@ -8205,9 +8232,13 @@ components:
Querybuildertypesv5BucketOptionsLinear:
properties:
kind:
$ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
description: How the upper bounds are spaced.
spec:
$ref: '#/components/schemas/Querybuildertypesv5LinearBucketsSpec'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5LinearBucketsSpec'
description: The evenly spaced bucket specification.
required:
- kind
- spec
@@ -8215,9 +8246,13 @@ components:
Querybuildertypesv5BucketOptionsLog:
properties:
kind:
$ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
description: How the upper bounds are spaced.
spec:
$ref: '#/components/schemas/Querybuildertypesv5LogBucketsSpec'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5LogBucketsSpec'
description: The logarithmic bucket specification.
required:
- kind
- spec
@@ -8806,54 +8841,78 @@ components:
Querybuildertypesv5QueryEnvelopeBuilder:
properties:
spec:
$ref: '#/components/schemas/Querybuildertypesv5BuilderQuerySpec'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5BuilderQuerySpec'
description: The builder query specification.
type:
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
description: The type of the query.
required:
- type
type: object
Querybuildertypesv5QueryEnvelopeBuilderAI:
properties:
spec:
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregation'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregation'
description: The AI builder query specification.
type:
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
description: The type of the query.
required:
- type
type: object
Querybuildertypesv5QueryEnvelopeClickHouseSQL:
properties:
spec:
$ref: '#/components/schemas/Querybuildertypesv5ClickHouseQuery'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5ClickHouseQuery'
description: The ClickHouse SQL query specification.
type:
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
description: The type of the query.
required:
- type
type: object
Querybuildertypesv5QueryEnvelopeFormula:
properties:
spec:
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderFormula'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderFormula'
description: The formula specification.
type:
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
description: The type of the query.
required:
- type
type: object
Querybuildertypesv5QueryEnvelopePromQL:
properties:
spec:
$ref: '#/components/schemas/Querybuildertypesv5PromQuery'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5PromQuery'
description: The PromQL query specification.
type:
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
description: The type of the query.
required:
- type
type: object
Querybuildertypesv5QueryEnvelopeTraceOperator:
properties:
spec:
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderTraceOperator'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderTraceOperator'
description: The trace operator specification.
type:
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
description: The type of the query.
required:
- type
type: object
@@ -9413,9 +9472,13 @@ components:
RuletypesEvaluationCumulative:
properties:
kind:
$ref: '#/components/schemas/RuletypesEvaluationKind'
allOf:
- $ref: '#/components/schemas/RuletypesEvaluationKind'
description: The kind of evaluation.
spec:
$ref: '#/components/schemas/RuletypesCumulativeWindow'
allOf:
- $ref: '#/components/schemas/RuletypesCumulativeWindow'
description: The cumulative window evaluation specification.
required:
- kind
- spec
@@ -9438,9 +9501,13 @@ components:
RuletypesEvaluationRolling:
properties:
kind:
$ref: '#/components/schemas/RuletypesEvaluationKind'
allOf:
- $ref: '#/components/schemas/RuletypesEvaluationKind'
description: The kind of evaluation.
spec:
$ref: '#/components/schemas/RuletypesRollingWindow'
allOf:
- $ref: '#/components/schemas/RuletypesRollingWindow'
description: The rolling window evaluation specification.
required:
- kind
- spec
@@ -9821,9 +9888,13 @@ components:
RuletypesThresholdBasic:
properties:
kind:
$ref: '#/components/schemas/RuletypesThresholdKind'
allOf:
- $ref: '#/components/schemas/RuletypesThresholdKind'
description: The kind of threshold.
spec:
$ref: '#/components/schemas/RuletypesBasicRuleThresholds'
allOf:
- $ref: '#/components/schemas/RuletypesBasicRuleThresholds'
description: The basic threshold specification (array of thresholds).
required:
- kind
- spec
@@ -10595,13 +10666,11 @@ components:
items:
$ref: '#/components/schemas/SpantypesEvent'
type: array
formatted_input:
formattedInput:
$ref: '#/components/schemas/AiobservabilitytypesInputMessages'
formatted_output:
formattedOutput:
$ref: '#/components/schemas/AiobservabilitytypesOutputMessages'
formatter:
type: string
formatter_warnings:
formatterWarnings:
items:
type: string
type: array
@@ -10647,10 +10716,9 @@ components:
- attributes
- events
- references
- formatted_input
- formatted_output
- formatter
- formatter_warnings
- formattedInput
- formattedOutput
- formatterWarnings
type: object
SpantypesTraceAISummary:
properties:
@@ -10731,6 +10799,14 @@ components:
flags:
minimum: 0
type: integer
formattedInput:
$ref: '#/components/schemas/AiobservabilitytypesInputMessages'
formattedOutput:
$ref: '#/components/schemas/AiobservabilitytypesOutputMessages'
formatterWarnings:
items:
type: string
type: array
has_children:
type: boolean
has_error:
@@ -14145,6 +14221,67 @@ paths:
tags:
- promote
x-signoz-stability: alpha
/api/v1/promoted_paths/materialized:
post:
deprecated: false
description: This endpoint indexes the JSON sub-columns of materialized paths
whose columns are indexed and returns the indexed and skipped paths.
operationId: IndexMaterializedPaths
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PromotetypesPostableIndexMaterializedPaths'
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/PromotetypesIndexMaterializedPathsResult'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- logs-field:update
- traces-field:update
- tokenizer:
- logs-field:update
- traces-field:update
summary: Index materialized paths
tags:
- promote
x-signoz-stability: alpha
/api/v1/roles:
get:
deprecated: false
@@ -16137,10 +16274,8 @@ paths:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
- api_key: []
- tokenizer: []
summary: Get stats
tags:
- stats
@@ -16518,12 +16653,12 @@ paths:
/api/v1/traces/{traceID}/thread:
get:
deprecated: false
description: Returns the spans carrying gen_ai input or output messages, or
a tool execution, in timestamp order. Messages already in the OTel GenAI shape
are decoded into formatted_input and formatted_output, with formatter naming
the converter and formatter_warnings what it could not resolve. Pass nextCursor
as after or prevCursor as before to page, or spanId to open the page around
a span.
description: Returns the gen_ai spans of a trace, model calls, tool calls and
agent spans, in timestamp order. The messages a span carries are decoded into
formattedInput and formattedOutput, with formatterWarnings holding what the
converter could not resolve; a span without messages has both empty. Pass
nextCursor as after or prevCursor as before to page, or spanId to open the
page around a span.
operationId: GetTraceThread
parameters:
- description: Page size, at most 100. 0 means 20.
@@ -18989,9 +19124,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: Run Infra Monitoring Setup Checks
tags:
- inframonitoring
@@ -19063,9 +19198,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Clusters for Infra Monitoring
tags:
- inframonitoring
@@ -19147,9 +19282,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List DaemonSets for Infra Monitoring
tags:
- inframonitoring
@@ -19225,9 +19360,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Deployments for Infra Monitoring
tags:
- inframonitoring
@@ -19293,9 +19428,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Hosts for Infra Monitoring
tags:
- inframonitoring
@@ -19375,9 +19510,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Jobs for Infra Monitoring
tags:
- inframonitoring
@@ -19456,9 +19591,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Kubernetes Containers for Infra Monitoring
tags:
- inframonitoring
@@ -19527,9 +19662,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Namespaces for Infra Monitoring
tags:
- inframonitoring
@@ -19602,9 +19737,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Nodes for Infra Monitoring
tags:
- inframonitoring
@@ -19678,9 +19813,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Pods for Infra Monitoring
tags:
- inframonitoring
@@ -19749,9 +19884,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Volumes for Infra Monitoring
tags:
- inframonitoring
@@ -19827,9 +19962,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List StatefulSets for Infra Monitoring
tags:
- inframonitoring
@@ -24997,6 +25132,75 @@ paths:
tags:
- rules
x-signoz-stability: alpha
/api/v2/rules/{id}/clone:
post:
deprecated: false
description: This endpoint creates a new alert rule from the stored definition
of an existing one. The clone keeps every modeled field of the source, including
its enabled/disabled state, and gets the source's name suffixed with " - Copy"
(or a bumped " - Copy (n)" counter when the source is itself a copy). The
clone is owned by the caller.
operationId: CloneRuleByID
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/RuletypesRule'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- EDITOR
- tokenizer:
- EDITOR
summary: Clone alert rule
tags:
- rules
x-signoz-stability: alpha
/api/v2/rules/{id}/history/filter_keys:
get:
deprecated: false
@@ -28667,7 +28871,9 @@ paths:
deprecated: false
description: Returns the waterfall view of spans including all spans if total
spans are under a limit, a max count otherwise. Aggregations are dropped compared
to v3
to v3. A span carrying gen_ai input or output messages also has them decoded
into formattedInput and formattedOutput, with formatterWarnings holding what
the converter could not resolve.
operationId: GetWaterfallV4
parameters:
- in: path

View File

@@ -20,6 +20,7 @@ We **recommend** (almost enforce) reviewing these guides before contributing to
- [Provider](provider.md) - Dependency injection and provider patterns
- [Packages](packages.md) - Naming, layout, and conventions for `pkg/` packages
- [Service](service.md) - Managed service lifecycle with `factory.Service`
- [Span Mapper](spanmapper.md) - Provenance of the shipped span mapper definitions and what is left unmapped
- [SQL](sql.md) - Database and SQL patterns
- [DSL Filtering to SQL](dslfilteringtosql.md) - Compiling the list filter DSL to relational-store WHERE clauses
- [Types](types.md) - Domain types, request/response bodies, and storage rows in `pkg/types/`

View File

@@ -0,0 +1,62 @@
# Span Mapper
Where each source key in the shipped definitions under `pkg/modules/spanmapper/implspanmapper/fs/definitions/` comes from. A group runs on spans with an attribute key containing one of its condition substrings; sources are tried by priority and the first present wins; an existing target is never overwritten. Bump `version` on every change.
## gen_ai.llm
Condition: `model`, `llm.input_messages` (OpenInference sets `llm.model_name` from the response, so a failed call has no model key).
| Source | Emitted by |
|---|---|
| `llm.model_name`, `llm.provider`, `llm.system`, `llm.token_count.prompt`, `llm.token_count.completion`, `llm.token_count.prompt_details.cache_read`, `llm.token_count.prompt_details.cache_write`, `llm.token_count.completion_details.reasoning`, `llm.finish_reason`, `input.value`, `output.value`, `embedding.model_name`, `reranker.model_name`, `openinference.span.kind` | OpenInference |
| `llm.request.model`, `llm.response.model`, `llm.vendor`, `llm.usage.prompt_tokens`, `llm.usage.completion_tokens`, `llm.response.finish_reason`, `llm.response.stop_reason` | OpenLLMetry before the `gen_ai` rename |
| `gen_ai.system`, `gen_ai.usage.prompt_tokens`, `gen_ai.usage.completion_tokens`, `gen_ai.usage.cache_read_input_tokens`, `gen_ai.usage.cache_creation_input_tokens`, `gen_ai.usage.reasoning_tokens`, `gen_ai.prompt`, `gen_ai.completion`, `llm.request.type`, `traceloop.span.kind` | OpenLLMetry, LiteLLM, CrewAI, Mistral, LangSmith, OpenRouter gateway |
| `gen_ai.usage.cache_write.input_tokens` | OTel semconv draft spelling, Codex |
| `gen_ai.response.finish_reason`, `gen_ai.usage.prompt_token_details.cached_read_tokens`, `gen_ai.usage.completion_token_details.reasoning_tokens` | Bifrost gateway |
| `gen_ai.usage.input_tokens.cached` | OpenRouter gateway |
| `gen_ai.response.model`, last source for the request model | vLLM, which writes only the response model |
| `ai.model.id`, `ai.model.provider`, `ai.response.model`, `ai.response.finishReason`, `ai.prompt.messages`, `ai.response.text`, `ai.response.toolCalls`, `ai.usage.promptTokens`, `ai.usage.completionTokens` | Vercel AI SDK v4, Mastra |
| `ai.usage.inputTokens`, `ai.usage.outputTokens`, `ai.usage.cachedInputTokens`, `ai.usage.reasoningTokens` | Vercel AI SDK v5 |
| `langfuse.observation.model.name`, `langfuse.session.id` | Langfuse SDK v3 |
| `session.id` | OpenInference, Langfuse, Claude Code |
| `gen_ai.client.session_id` | opentelemetry-hooks runner (Cursor, Copilot, Claude Code hooks) |
| `model`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_creation_tokens` | Claude Code `claude_code.llm_request` spans ([docs](https://code.claude.com/docs/en/monitoring-usage#span-attributes)) |
## gen_ai.tool
Condition: `tool.name` (OpenInference), `ai.toolCall.` (Vercel, Mastra), `tool_call_args`, `tool_response` (Google ADK), `tool_use_id` (Claude Code, opentelemetry-hooks). Not `tool_name`: Codex records it on the model call span.
| Source | Emitted by |
|---|---|
| `tool.name`, `tool.id`, `tool.description`, `input.value`, `output.value`, `openinference.span.kind` | OpenInference |
| `ai.toolCall.name`, `ai.toolCall.id`, `ai.toolCall.args`, `ai.toolCall.result` | Vercel AI SDK, Mastra |
| `traceloop.entity.input`, `traceloop.entity.output`, `traceloop.span.kind`, `llm.request.type` | OpenLLMetry |
| `gcp.vertex.agent.tool_call_args`, `gcp.vertex.agent.tool_response` | Google ADK |
| `tool_name` | Claude Code `claude_code.tool` spans |
| `gen_ai.client.tool_name`, `gen_ai.client.tool_use_id` | opentelemetry-hooks runner (Cursor, Copilot, Claude Code hooks) |
## gen_ai.agent
Condition: `agent`.
| Source | Emitted by |
|---|---|
| `agent.name`, `agent.id`, `agent.description`, `openinference.span.kind` | OpenInference; `agent.id` and `agent.description` seen in no capture |
| `agent_name`, `final_result` | Pydantic AI |
| `crewai.agent.role`, `crewai.agent.id` | CrewAI |
| `traceloop.span.kind`, `llm.request.type` | OpenLLMetry |
| `agent_id` | Claude Code |
## Coverage of documented integrations
Every integration under [signoz.io/docs/llm-observability](https://signoz.io/docs/llm-observability/) was checked by looking at the instrumentation its page installs and the keys that instrumentation writes.
- Most pages install OpenInference or OpenLLMetry, or an SDK that writes OTel GenAI semconv itself (OpenAI, OpenAI Agents, Pydantic AI, AutoGen, Microsoft Agent Framework, Semantic Kernel, Copilot Chat, LiteLLM, OpenRouter). Their keys are the rows above.
- Three write their own keys and have their own rows: Claude Code and Claude Agent SDK, Cursor through opentelemetry-hooks, vLLM (response model only).
- Codex cannot be priced by renaming: `model` sits on the sampling function spans, `gen_ai.usage.*` on the child `handle_responses` span, `conversation.id` on the `codex.sampling` phase span, never together on one span.
To check a new integration, capture a trace and look for a model, usage, tool or messages key that no row above lists.
## Left unmapped on purpose
LiteLLM `llm.openai.messages` and `llm.openai.choices` (debug echo of the parent span's messages, Python repr). Total tokens and vendor cost keys (no target; the pricing processor owns cost). `ai.response.msToFirstChunk` and `gen_ai.latency.time_to_first_token` (milliseconds and seconds under one target).

View File

@@ -86,6 +86,9 @@ tests/
├── .env.local # generated by bootstrap/setup.py (gitignored)
├── bootstrap/
│ └── setup.py # test_setup / test_teardown — pytest lifecycle
├── drivers/ # the driver kit and one driver per feature
│ ├── kit.ts
│ └── routing.ts
├── fixtures/ # Playwright test fixtures (test.extend) only
│ └── auth.ts
├── helpers/ # function helpers + the constants they share with tests
@@ -112,6 +115,24 @@ These two folders look similar but mean different things:
Rule of thumb: if it's a `test.extend` fixture, put it in `fixtures/`. If it's a function you call explicitly (or a constant the function uses), put it in `helpers/`. If it's a static file the helpers read, put it in `testdata/`.
### Drivers
New suites use a driver rather than loose helpers. A driver is a function that closes over the page and returns the feature's actions and locators. `drivers/kit.ts` turns it into a fixture, so a spec imports one module and never passes a `page` around:
```ts
import { expect, PATH, test } from '../../drivers/routing';
test('TC-09 RouteTab switches the url', async ({ routing }) => {
await routing.goto(PATH.metricsSummary);
await expect(routing.activeRouteTab(PATH.metricsSummary)).toBeVisible();
});
```
- `driver(name, build)` adds a test-scoped fixture built on `authedPage`.
- `seeded(name, open, close)` adds a worker-scoped seed. `open` and `close` run on a throwaway admin page.
- A driver reuses helpers by calling them, never by reaching for another fixture.
- `drivers/` is AI-owned: coding agents generate and maintain it with the specs, and `.gitattributes` collapses it in diffs.
### Extended fixtures
For features needing complex setup (API-seeded data, ruler evaluation waits, cleanup), create domain-specific fixtures that extend `auth`. Group them in `fixtures/<domain>/`.

View File

@@ -7,66 +7,24 @@ import (
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type gcpcloudprovider struct {
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
}
func NewGCPCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) cloudintegration.CloudProviderModule {
return &gcpcloudprovider{
serviceDefinitions: defStore,
}
}
func (g *gcpcloudprovider) BuildIntegrationConfig(
ctx context.Context,
account *cloudintegrationtypes.Account,
services []*cloudintegrationtypes.StorableCloudIntegrationService,
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
if account.Config.GCP.ConnectionMethod != cloudintegrationtypes.GCPConnectionMethodAgent {
return &cloudintegrationtypes.ProviderIntegrationConfig{}, nil
}
var metricNames []string
var sinkFilters []string
for _, storedSvc := range services {
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(cloudintegrationtypes.CloudProviderTypeGCP, storedSvc.Config)
if err != nil {
return nil, err
}
svcDef, err := g.GetServiceDefinition(ctx, storedSvc.Type)
if err != nil {
return nil, err
}
if svcDef.TelemetryCollectionStrategy.GCP == nil {
continue
}
strategy := svcDef.TelemetryCollectionStrategy.GCP
if svcCfg.IsMetricsEnabled(cloudintegrationtypes.CloudProviderTypeGCP) && strategy.Metrics != nil {
metricNames = append(metricNames, strategy.Metrics.MetricNames...)
}
if svcCfg.IsLogsEnabled(cloudintegrationtypes.CloudProviderTypeGCP) && strategy.Logs != nil {
sinkFilters = append(sinkFilters, strategy.Logs.SinkFilter)
}
}
return &cloudintegrationtypes.ProviderIntegrationConfig{
GCP: cloudintegrationtypes.NewGCPIntegrationConfig(account.Config.GCP, metricNames, sinkFilters),
}, nil
func (g *gcpcloudprovider) BuildIntegrationConfig(ctx context.Context, account *cloudintegrationtypes.Account, services []*cloudintegrationtypes.StorableCloudIntegrationService) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
// for manual flow we don't have any integration config to return, so returning empty config for now.
return &cloudintegrationtypes.ProviderIntegrationConfig{}, nil
}
func (g *gcpcloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
if account.Config.GCP.ConnectionMethod != cloudintegrationtypes.GCPConnectionMethodAgent {
return &cloudintegrationtypes.ConnectionArtifact{}, nil
}
connectionArtifact, err := cloudintegrationtypes.NewGCPConnectionArtifact(account.ID, req.Config.AgentVersion, req.Credentials, account.Config.GCP)
if err != nil {
return nil, err
}
return &cloudintegrationtypes.ConnectionArtifact{
GCP: connectionArtifact,
}, nil
// for manual flow we don't have any connection artifact to return, so returning empty artifact for now.
return &cloudintegrationtypes.ConnectionArtifact{}, nil
}
func (g *gcpcloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
@@ -76,7 +34,3 @@ func (g *gcpcloudprovider) GetServiceDefinition(ctx context.Context, serviceID c
func (g *gcpcloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
return g.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeGCP)
}
type gcpcloudprovider struct {
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
}

View File

@@ -2,6 +2,7 @@ package implcloudintegration
import (
"context"
"fmt"
"time"
"github.com/SigNoz/signoz/pkg/errors"
@@ -170,6 +171,18 @@ func (module *module) AgentCheckIn(ctx context.Context, orgID valuer.UUID, provi
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
connectedAccount, err := module.store.GetConnectedAccountByProviderAccountID(ctx, orgID, req.ProviderAccountID, provider)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return nil, err
}
// If a different integration is already connected to this provider account ID, reject the check-in.
// Allow re-check-in from the same integration (e.g. agent restarting).
if connectedAccount != nil && connectedAccount.ID != req.CloudIntegrationID {
errMessage := fmt.Sprintf("provider account id %s is already connected to cloud integration id %s", req.ProviderAccountID, connectedAccount.ID)
return nil, errors.New(errors.TypeAlreadyExists, cloudintegrationtypes.ErrCodeCloudIntegrationAlreadyConnected, errMessage)
}
storableAccount, err := module.store.GetAccountByID(ctx, orgID, req.CloudIntegrationID, provider)
if err != nil {
return nil, err
@@ -182,36 +195,41 @@ func (module *module) AgentCheckIn(ctx context.Context, orgID valuer.UUID, provi
syncState := account.NextSyncState(req.SyncedVersion)
// If account has been removed (disconnected), return a minimal response with empty integration config.
if account.RemovedAt != nil {
return module.checkInRemovedAccount(ctx, account, req.ProviderAccountID, syncState)
}
// Heartbeat stays frozen after removal, only the sync state is updated.
if account.AgentReport != nil && syncState != nil {
account.UpdateSyncState(syncState)
if err := account.ErrIfInvalidProviderAccountID(req.ProviderAccountID); err != nil {
return nil, err
}
storableAccount, err = cloudintegrationtypes.NewStorableCloudIntegration(account)
if err != nil {
return nil, err
}
connectedAccount, err := module.store.GetConnectedAccountByProviderAccountID(ctx, orgID, req.ProviderAccountID, provider)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return nil, err
}
err = module.store.UpdateAgentReport(ctx, storableAccount)
if err != nil {
return nil, err
}
}
// If a different integration is already connected to this provider account ID, reject the check-in.
// Allow re-check-in from the same integration (e.g. agent restarting).
if connectedAccount != nil && connectedAccount.ID != req.CloudIntegrationID {
return nil, errors.Newf(
errors.TypeAlreadyExists,
cloudintegrationtypes.ErrCodeCloudIntegrationAlreadyConnected,
"provider account id %s is already connected to cloud integration id %s", req.ProviderAccountID, connectedAccount.ID,
)
return cloudintegrationtypes.NewAgentCheckInResponse(
req.ProviderAccountID,
account.ID.StringValue(),
new(cloudintegrationtypes.ProviderIntegrationConfig),
account.RemovedAt,
syncState,
), nil
}
account.UpdateAgentReport(&req.ProviderAccountID, cloudintegrationtypes.NewAgentReport(req.Data, syncState))
storableAccount, err = cloudintegrationtypes.NewStorableCloudIntegration(account)
if err != nil {
return nil, err
}
if err := module.store.UpdateAgentReport(ctx, storableAccount); err != nil {
err = module.store.UpdateAgentReport(ctx, storableAccount)
if err != nil {
return nil, err
}
@@ -480,45 +498,11 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
stats["cloudintegration.azure.connectedaccounts.count"] = azureAccountsCount
}
gcpAccountsCount, err := module.store.CountConnectedAccounts(ctx, orgID, cloudintegrationtypes.CloudProviderTypeGCP)
if err == nil {
stats["cloudintegration.gcp.connectedaccounts.count"] = gcpAccountsCount
}
// NOTE: not adding stats for services for now.
return stats, nil
}
// checkInRemovedAccount returns an empty integration config and saves cleanup acknowledgments without refreshing the heartbeat.
func (module *module) checkInRemovedAccount(ctx context.Context, account *cloudintegrationtypes.Account, providerAccountID string, syncState *cloudintegrationtypes.SyncState) (*cloudintegrationtypes.AgentCheckInResponse, error) {
response := cloudintegrationtypes.NewAgentCheckInResponse(
providerAccountID,
account.ID.StringValue(),
new(cloudintegrationtypes.ProviderIntegrationConfig),
account.RemovedAt,
syncState,
)
if account.AgentReport == nil {
return response, nil
}
if syncState == nil {
return response, nil
}
account.UpdateSyncState(syncState)
storableAccount, err := cloudintegrationtypes.NewStorableCloudIntegration(account)
if err != nil {
return nil, err
}
if err := module.store.UpdateAgentReport(ctx, storableAccount); err != nil {
return nil, err
}
return response, nil
}
func (module *module) getCloudProvider(provider cloudintegrationtypes.CloudProviderType) (cloudintegration.CloudProviderModule, error) {
if cloudProviderModule, ok := module.cloudProvidersMap[provider]; ok {
return cloudProviderModule, nil

View File

@@ -277,10 +277,7 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (int, error) {
lb.Set(ruletypes.AlertRuleIDLabel, r.ID())
lb.Set(ruletypes.RuleSourceLabel, r.GeneratorURL())
annotations := make(ruletypes.Labels, 0, len(r.Annotations().Map()))
for name, value := range r.Annotations().Map() {
annotations = append(annotations, ruletypes.Label{Name: name, Value: expand(value)})
}
annotations := r.ExpandAnnotations(expand)
if smpl.IsMissing {
lb.Set(ruletypes.AlertNameLabel, "[No data] "+r.Name())
lb.Set(ruletypes.NoDataLabel, "true")
@@ -423,7 +420,7 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (int, error) {
func (r *AnomalyRule) String() string {
ar := ruletypes.PostableRule{
AlertName: r.Name(),
AlertName: valuer.NewDisplayName(r.Name()),
RuleCondition: r.Condition(),
EvalWindow: r.EvalWindow(),
Labels: r.Labels().Map(),

View File

@@ -46,7 +46,7 @@ func TestAnomalyRule_NoData_AlertOnAbsent(t *testing.T) {
target := 500.0
postableRule := ruletypes.PostableRule{
AlertName: "Test anomaly no data",
AlertName: valuer.NewDisplayName("Test anomaly no data"),
AlertType: ruletypes.AlertTypeMetric,
RuleType: ruletypes.RuleTypeAnomaly,
Evaluation: &ruletypes.EvaluationEnvelope{Kind: ruletypes.RollingEvaluation, Spec: ruletypes.RollingWindow{
@@ -149,7 +149,7 @@ func TestAnomalyRule_NoData_AbsentFor(t *testing.T) {
target := 500.0
postableRule := ruletypes.PostableRule{
AlertName: "Test anomaly no data with AbsentFor",
AlertName: valuer.NewDisplayName("Test anomaly no data with AbsentFor"),
AlertType: ruletypes.AlertTypeMetric,
RuleType: ruletypes.RuleTypeAnomaly,
Evaluation: &ruletypes.EvaluationEnvelope{Kind: ruletypes.RollingEvaluation, Spec: ruletypes.RollingWindow{

View File

@@ -13,6 +13,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) {
@@ -118,7 +119,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
}
parsedRule := opts.Rule
var alertname = parsedRule.AlertName
var alertname = parsedRule.AlertName.StringValue()
if alertname == "" {
// alertname is not mandatory for testing, so picking
// a random string here
@@ -126,7 +127,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
}
// append name to indicate this is test alert
parsedRule.AlertName = fmt.Sprintf("%s%s", alertname, ruletypes.TestAlertPostFix)
parsedRule.AlertName = valuer.NewDisplayName(fmt.Sprintf("%s%s", alertname, ruletypes.TestAlertPostFix))
var rule baserules.Rule
var err error

View File

@@ -262,23 +262,20 @@
// React Hooks rules - Enforces Rules of Hooks (only call at top level)
"react-hooks/exhaustive-deps": "warn",
// Warns about missing dependencies in useEffect/useMemo/useCallback
// NOTE: The following react-hooks rules are not supported right know, follow the progress at https://github.com/oxc-project/oxc/issues/1022
// Most of them are for React Compiler, which we don't have enabled right know
// "react-hooks/config": "error",
// "react-hooks/error-boundaries": "error",
// "react-hooks/component-hook-factories": "error",
// "react-hooks/gating": "error",
// "react-hooks/globals": "error",
// "react-hooks/immutability": "error",
// "react-hooks/preserve-manual-memoization": "error",
// "react-hooks/purity": "error",
// "react-hooks/refs": "error",
// "react-hooks/set-state-in-effect": "error",
// "react-hooks/set-state-in-render": "error",
// "react-hooks/static-components": "error",
// "react-hooks/unsupported-syntax": "warn",
// "react-hooks/use-memo": "error",
// "react-hooks/incompatible-library": "warn",
// React Compiler rules, off because we don't have React Compiler enabled
"react/error-boundaries": "off",
"react/globals": "off",
"react/immutability": "off",
"react/incompatible-library": "off",
"react/preserve-manual-memoization": "off",
"react/purity": "off",
"react/refs": "off",
"react/set-state-in-effect": "off",
"react/set-state-in-render": "off",
"react/static-components": "off",
"react/unsupported-syntax": "off",
"react/use-memo": "off",
"react/void-use-memo": "off",
"signoz/no-unsupported-asset-pattern": "error",
// Prevents the wrong usage of assets to break custom base path installations
"signoz/no-zustand-getstate-in-hooks": "error",
@@ -582,6 +579,16 @@
"rules": {
"signoz/no-dashboard-fetch-outside-root": "off"
}
},
{
// TypeScript reports real redeclarations; this rule also flags `const X` + `type X` pairs.
"files": [
"**/*.ts",
"**/*.tsx"
],
"rules": {
"no-redeclare": "off"
}
}
]
}

View File

@@ -119,7 +119,7 @@ const preview: Preview = {
'Planned Downtime',
'Routing Policies',
'Channels',
['List', 'New', 'Edit'],
['List', 'List Authz', 'New', 'New Authz', 'Edit', 'Edit Authz'],
],
'Dashboards',
[

View File

@@ -25,6 +25,7 @@ You are operating within a constrained context window and strict system prompts.
- Never create barrel files.
- When writing new css, prefer CSS Modules
- Use ./docs/css-modules-guide.md as reference on how to write good CSS Modules.
- Before styling a `@signozhq/ui` component, check its props in `node_modules/@signozhq/ui/dist/<component>/*.d.ts` for `className`/`style` support; some omit them. Avoid overriding the component's CSS vars unless explicitly needed.
- When writing code that could need authorization checks, read ./src/lib/authz/README.md
3. FORCED VERIFICATION: Your internal tools mark file writes as successful even if the code does not compile. You are FORBIDDEN from reporting a task as complete until you have:

View File

@@ -41,6 +41,12 @@ These hold for every page. The per-pattern sections below only add to them.
`create` on API Keys, so blocking the outer container would hide work they are allowed to do.
7. **Verbs not covered here** (`attach`, `detach`, `assignee`) behave like `delete`: gate the control that triggers
them, not the surrounding content.
8. **Expose the selector.** A role is scoped to a resource by pasting its selector into the role editor, so every
resource a role can be scoped to shows that selector with a copy control on its detail view or drawer. The selector
is whatever the editor accepts for the resource (dashboard ID, service account ID, role name, ...), listed per
resource in [permissions.config.ts](../src/container/RolesSettings/permissions.config.ts). It is already known from
the row or URL, so it stays visible and copyable without `read`. Reference: the ID field in
[ServiceAccountDrawer/OverviewTab.tsx](../src/components/ServiceAccountDrawer/OverviewTab.tsx).
## Page patterns
@@ -136,6 +142,8 @@ Use the existing components. Create a new one only if none fit.
- [ ] Am I gating a shared component rather than a page or section?
- If so, it must stay functional with no permission. Example: the Query Builder works without field suggestions when
the user cannot read them.
- [ ] Does the page expose the resource's selector with a copy control (rule 8)?
- The expected format is the `selectorPlaceholder` for the resource in `permissions.config.ts`.
## Testing

View File

@@ -41,6 +41,20 @@ if (!HTMLElement.prototype.releasePointerCapture) {
HTMLElement.prototype.releasePointerCapture = function (): void {};
}
// jsdom has no PointerEvent; Base UI Switch constructs one on click.
if (typeof window.PointerEvent === 'undefined') {
class PointerEventMock extends MouseEvent {
pointerId: number;
pointerType: string;
constructor(type: string, init: PointerEventInit = {}) {
super(type, init);
this.pointerId = init.pointerId ?? 0;
this.pointerType = init.pointerType ?? '';
}
}
(window as any).PointerEvent = PointerEventMock;
}
if (typeof window.IntersectionObserver === 'undefined') {
class IntersectionObserverMock {
observe(): void {}

View File

@@ -49,12 +49,13 @@
"@dnd-kit/sortable": "8.0.0",
"@dnd-kit/utilities": "3.2.2",
"@grafana/data": "^11.6.15",
"@lezer/highlight": "1.2.1",
"@monaco-editor/react": "^4.7.0",
"@sentry/react": "10.57.0",
"@sentry/vite-plugin": "5.3.0",
"@signozhq/design-tokens": "2.1.6",
"@signozhq/design-tokens": "2.2.1",
"@signozhq/icons": "0.4.0",
"@signozhq/ui": "0.1.0",
"@signozhq/ui": "0.2.0",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",
@@ -203,9 +204,9 @@
"lint-staged": "^17.0.4",
"msw": "1.3.2",
"orval": "8.22.0",
"oxfmt": "0.54.0",
"oxlint": "1.69.0",
"oxlint-tsgolint": "0.23.0",
"oxfmt": "0.73.0",
"oxlint": "1.88.0",
"oxlint-tsgolint": "7.0.2004",
"postcss": "8.5.26",
"postcss-scss": "4.0.9",
"react-resizable": "3.0.4",

844
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
"loading_channels_message": "Loading Channels..",
"page_title_create": "New Notification Channel",
"page_title_edit": "Edit Notification Channel",
"page_title_view": "View Notification Channel",
"button_save_channel": "Save",
"button_test_channel": "Test",
"button_return": "Back",
@@ -137,4 +138,4 @@
"api_key_required": "API Key is mandatory",
"to_required": "To field is mandatory",
"channel_name_required": "Channel name is mandatory"
}
}

View File

@@ -8,13 +8,21 @@
"button_new_channel": "New Alert Channel",
"tooltip_notification_channels": "More details on how to setting notification channels",
"sending_channels_note": "The alerts will be sent to all the configured channels.",
"channel_load_failed": "This channel could not be opened",
"loading_channels_message": "Loading Channels..",
"page_title_create": "New Notification Channel",
"page_title_edit": "Edit Notification Channel",
"page_title_view": "View Notification Channel",
"button_save_channel": "Save",
"button_test_channel": "Test",
"button_return": "Back",
"section_general": "General",
"section_connection": "Connection",
"section_message": "Message",
"section_advanced": "Advanced",
"section_recipients": "Recipients",
"field_channel_name": "Name",
"help_channel_name_immutable": "Renaming is blocked while alert rules and routing policies still reference a channel by name.",
"field_send_resolved": "Send resolved alerts",
"field_channel_type": "Type",
"field_webhook_url": "Webhook URL",
@@ -95,9 +103,44 @@
"placeholder_incidentio_metadata_key": "Key",
"placeholder_incidentio_metadata_value": "Value",
"button_incidentio_add_metadata": "Add metadata",
"button_incidentio_remove_metadata": "Remove metadata",
"field_slack_recipient": "Recipient",
"field_slack_title": "Title",
"field_slack_description": "Description",
"field_slack_title_link": "Title link",
"field_slack_color": "Color",
"help_slack_color": "good, warning, danger, or a hex value like #439FE0. Templates are allowed.",
"placeholder_slack_color": "{{ if eq .Status \"firing\" }}danger{{ else }}good{{ end }}",
"field_slack_pretext": "Pretext",
"help_slack_pretext": "Shown above the attachment block",
"field_slack_fallback": "Fallback text",
"help_slack_fallback": "Plain text shown where the attachment cannot render, such as push notifications",
"field_slack_footer": "Footer",
"field_slack_fields": "Fields",
"help_slack_fields": "Extra entries rendered as a table inside the attachment",
"placeholder_slack_field_title": "Title",
"placeholder_slack_field_value": "Value",
"field_slack_field_short": "Short",
"add_slack_field": "Add field",
"remove_slack_field": "Remove field",
"field_slack_actions": "Actions",
"help_slack_actions": "Buttons rendered under the attachment. A button with a URL links out.",
"label_slack_action_text": "Text",
"label_slack_action_url": "URL",
"label_slack_action_type": "Type",
"label_slack_action_name": "Name",
"label_slack_action_value": "Value",
"label_slack_action_style": "Style",
"label_slack_action_confirm": "Confirm",
"placeholder_slack_action_text": "Button text",
"placeholder_slack_action_url": "https://runbook.example.com",
"placeholder_slack_action_type": "button",
"placeholder_slack_action_name": "Name (Slack app callbacks)",
"placeholder_slack_action_value": "Value (Slack app callbacks)",
"placeholder_slack_action_style": "Style: default, primary or danger",
"placeholder_slack_action_confirm": "Confirmation prompt (optional)",
"add_slack_action": "Add action",
"remove_slack_action": "Remove action",
"field_opsgenie_api_key": "API Key",
"field_opsgenie_description": "Description",
"placeholder_opsgenie_description": "Description",
@@ -109,6 +152,7 @@
"placeholder_email_html": "Email body template",
"field_webhook_username": "User Name (optional)",
"field_webhook_password": "Password (optional)",
"field_webhook_bearer_token": "Bearer token (optional)",
"field_pager_routing_key": "Routing Key",
"field_pager_description": "Description",
"field_pager_severity": "Severity",
@@ -133,6 +177,7 @@
"help_pager_severity": "Severity of the incident, must be one of: must be one of the following: 'critical', 'warning', 'error' or 'info'",
"help_webhook_username": "Leave empty for bearer auth or when authentication is not necessary.",
"help_webhook_password": "Specify a password or bearer token",
"help_webhook_bearer_token": "Sent as an Authorization: Bearer header. Leave the username and password empty when using it.",
"help_pager_description": "Shows up as description in pagerduty",
"help_opsgenie_message": "Shows up as message in opsgenie",
"help_opsgenie_priority": "Priority of the incident",
@@ -148,8 +193,10 @@
"channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly",
"channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again",
"webhook_url_required": "Webhook URL is mandatory",
"routing_key_required": "Routing Key is mandatory for creating pagerduty channel",
"pager_details_invalid_json": "Enter valid JSON, or clear the box to send no details",
"slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)",
"api_key_required": "API Key is mandatory",
"to_required": "To field is mandatory",
"channel_name_required": "Channel name is mandatory"
}
}

View File

@@ -300,20 +300,17 @@ describe('PrivateRoute', () => {
);
});
it('should redirect /logs-save-views to /logs/saved-views', () => {
renderPrivateRoute({ initialRoute: '/logs-save-views' });
it.each([
['/logs-save-views', ROUTES.LOGS_EXPLORER],
['/traces-save-views', ROUTES.TRACES_EXPLORER],
[ROUTES.LOGS_SAVE_VIEWS, ROUTES.LOGS_EXPLORER],
[ROUTES.TRACES_SAVE_VIEWS, ROUTES.TRACES_EXPLORER],
[ROUTES.METRICS_EXPLORER_VIEWS, ROUTES.METRICS_EXPLORER_EXPLORER],
[ROUTES.METER_EXPLORER_VIEWS, ROUTES.METER_EXPLORER],
])('should redirect the old views route %s to %s', (from, to) => {
renderPrivateRoute({ initialRoute: from });
expect(screen.getByTestId('location-display')).toHaveTextContent(
'/logs/saved-views',
);
});
it('should redirect /traces-save-views to /traces/saved-views', () => {
renderPrivateRoute({ initialRoute: '/traces-save-views' });
expect(screen.getByTestId('location-display')).toHaveTextContent(
'/traces/saved-views',
);
expect(screen.getByTestId('location-display').textContent).toBe(to);
});
it('should redirect /settings/access-tokens to /settings/service-accounts', () => {
@@ -1454,10 +1451,9 @@ describe('PrivateRoute', () => {
await assertRedirectsTo(ROUTES.UN_AUTHORIZED);
});
it('should redirect VIEWER from /alerts/channels/new (ADMIN only)', async () => {
// After moving channels under /alerts, CHANNELS_NEW ('/alerts/channels/new')
// is an exact, ADMIN-only route with no overlapping non-exact ALL_CHANNELS
// route to match last, so a VIEWER is now correctly redirected.
it('lets a VIEWER reach /alerts/channels/new, which authz then gates', () => {
// CHANNELS_NEW runs on fine-grained authz, so the router no longer decides
// on the role: the page's own guard denies when `create` is not granted.
renderPrivateRoute({
initialRoute: ROUTES.CHANNELS_NEW,
appContext: {
@@ -1466,7 +1462,7 @@ describe('PrivateRoute', () => {
},
});
await assertRedirectsTo(ROUTES.UN_AUTHORIZED);
assertStaysOnRoute(ROUTES.CHANNELS_NEW);
});
it('should allow EDITOR to access /get-started-with-signoz-cloud route', () => {
@@ -1550,6 +1546,11 @@ describe('PrivateRoute', () => {
keyof typeof routeWithInitialAuthZSupport,
AuthzRouteCase
> = {
CHANNELS_NEW: { path: ROUTES.CHANNELS_NEW, deniedRoles: DENIED_ROLES },
CHANNELS_EDIT: {
path: ROUTES.CHANNELS_EDIT.replace(':channelId', 'channel-id-1'),
deniedRoles: DENIED_ROLES,
},
ALL_DASHBOARD: { path: ROUTES.ALL_DASHBOARD, deniedRoles: DENIED_ROLES },
DASHBOARD: {
path: ROUTES.DASHBOARD.replace(':dashboardId', 'dashboard-id-1'),
@@ -1635,11 +1636,6 @@ describe('PrivateRoute', () => {
['LIST_LICENSES', ROUTES.LIST_LICENSES, USER_ROLES.EDITOR as ROLES],
['ONBOARDING', ROUTES.ONBOARDING, USER_ROLES.VIEWER as ROLES],
['APPLICATION', ROUTES.APPLICATION, USER_ROLES.ANONYMOUS as ROLES],
[
'METRICS_EXPLORER_VIEWS',
ROUTES.METRICS_EXPLORER_VIEWS,
USER_ROLES.ANONYMOUS as ROLES,
],
];
it.each(authzRouteRolePairs)(

View File

@@ -513,8 +513,12 @@ export const LIST_LICENSES: AppRoutes = {
export const oldNewRoutesMapping: Record<string, string> = {
'/pipelines': '/logs/pipelines',
'/logs-explorer': '/logs/logs-explorer',
'/logs-save-views': '/logs/saved-views',
'/traces-save-views': '/traces/saved-views',
'/logs-save-views': '/logs/logs-explorer',
'/traces-save-views': '/traces-explorer',
'/logs/saved-views': '/logs/logs-explorer',
'/traces/saved-views': '/traces-explorer',
'/metrics-explorer/views': '/metrics-explorer/explorer',
'/meter/explorer/views': '/meter/explorer',
'/settings/access-tokens': '/settings/service-accounts',
'/settings/api-keys': '/settings/service-accounts',
'/messaging-queues': '/messaging-queues/overview',

View File

@@ -41,17 +41,16 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
message: data?.error,
};
}
// Avoid logging error when the request was just cancelled for whatever reason
if (isCancel(error)) {
return {
statusCode: 500,
payload: null,
error: 'Something went wrong',
message: null,
};
}
if (request) {
// Avoid logging error when the request was just cancelled for whatever reason
if (isCancel(error)) {
return {
statusCode: 500,
payload: null,
error: 'Something went wrong',
message: null,
};
}
// client never received a response, or request never left
console.error('client never received a response, or request never left');

View File

@@ -0,0 +1,24 @@
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError, CanceledError } from 'axios';
describe('ErrorResponseHandler', () => {
// axios aborts a request whose signal fired before it was sent without ever
// creating one, so the error carries neither `response` nor `request`.
it('treats a request cancelled before it was sent as a cancellation', () => {
const consoleError = jest
.spyOn(console, 'error')
.mockImplementation(() => undefined);
expect(ErrorResponseHandler(new CanceledError() as AxiosError)).toStrictEqual(
{
statusCode: 500,
payload: null,
error: 'Something went wrong',
message: null,
},
);
expect(consoleError).not.toHaveBeenCalled();
consoleError.mockRestore();
});
});

View File

@@ -220,8 +220,7 @@ export type ClarificationSummaryDTODiscoveredContextAnyOf = {
[key: string]: unknown;
};
export type ClarificationSummaryDTODiscoveredContext =
ClarificationSummaryDTODiscoveredContextAnyOf | null;
export type ClarificationSummaryDTODiscoveredContext = ClarificationSummaryDTODiscoveredContextAnyOf | null;
export interface ClarificationSummaryDTO {
/**
@@ -784,8 +783,7 @@ export type ThreadDetailResponseDTOActiveExecutionId = string | null;
export type ThreadDetailResponseDTOPendingApproval = ApprovalSummaryDTO | null;
export type ThreadDetailResponseDTOPendingClarification =
ClarificationSummaryDTO | null;
export type ThreadDetailResponseDTOPendingClarification = ClarificationSummaryDTO | null;
/**
* Full thread state for cold-start reconstruction (GET /threads/{id}).
@@ -975,8 +973,7 @@ export type ClarificationEventDTODiscoveredContextAnyOf = {
[key: string]: unknown;
};
export type ClarificationEventDTODiscoveredContext =
ClarificationEventDTODiscoveredContextAnyOf | null;
export type ClarificationEventDTODiscoveredContext = ClarificationEventDTODiscoveredContextAnyOf | null;
export interface ClarificationEventDTO {
/**
@@ -1299,8 +1296,7 @@ export type CreateThreadApiV1AssistantThreadsPostHeaders = {
'X-SigNoz-URL'?: string | null;
};
export type CreateThreadApiV1AssistantThreadsPostBody =
CreateThreadRequestDTO | null;
export type CreateThreadApiV1AssistantThreadsPostBody = CreateThreadRequestDTO | null;
export type ListThreadsApiV1AssistantThreadsGetParams = {
/**

View File

@@ -18,8 +18,10 @@ import type {
} from 'react-query';
import type {
IndexMaterializedPaths200,
ListPromotedPaths200,
ListPromotedPathsParams,
PromotetypesPostableIndexMaterializedPathsDTO,
PromotetypesPromotePathDTO,
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
@@ -230,3 +232,87 @@ export const usePromotePaths = <
> => {
return useMutation(getPromotePathsMutationOptions(options));
};
/**
* This endpoint indexes the JSON sub-columns of materialized paths whose columns are indexed and returns the indexed and skipped paths.
* @summary Index materialized paths
*/
export const indexMaterializedPaths = (
promotetypesPostableIndexMaterializedPathsDTO?: BodyType<PromotetypesPostableIndexMaterializedPathsDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<IndexMaterializedPaths200>({
url: `/api/v1/promoted_paths/materialized`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: promotetypesPostableIndexMaterializedPathsDTO,
signal,
});
};
export const getIndexMaterializedPathsMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof indexMaterializedPaths>>,
TError,
{ data?: BodyType<PromotetypesPostableIndexMaterializedPathsDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof indexMaterializedPaths>>,
TError,
{ data?: BodyType<PromotetypesPostableIndexMaterializedPathsDTO> },
TContext
> => {
const mutationKey = ['indexMaterializedPaths'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof indexMaterializedPaths>>,
{ data?: BodyType<PromotetypesPostableIndexMaterializedPathsDTO> }
> = (props) => {
const { data } = props ?? {};
return indexMaterializedPaths(data);
};
return { mutationFn, ...mutationOptions };
};
export type IndexMaterializedPathsMutationResult = NonNullable<
Awaited<ReturnType<typeof indexMaterializedPaths>>
>;
export type IndexMaterializedPathsMutationBody =
| BodyType<PromotetypesPostableIndexMaterializedPathsDTO>
| undefined;
export type IndexMaterializedPathsMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Index materialized paths
*/
export const useIndexMaterializedPaths = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof indexMaterializedPaths>>,
TError,
{ data?: BodyType<PromotetypesPostableIndexMaterializedPathsDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof indexMaterializedPaths>>,
TError,
{ data?: BodyType<PromotetypesPostableIndexMaterializedPathsDTO> },
TContext
> => {
return useMutation(getIndexMaterializedPathsMutationOptions(options));
};

View File

@@ -18,6 +18,8 @@ import type {
} from 'react-query';
import type {
CloneRuleByID201,
CloneRuleByIDPathParameters,
CreateRule201,
CreateRuleView201,
DeleteRuleByIDPathParameters,
@@ -963,6 +965,85 @@ export const useUpdateRuleByID = <
> => {
return useMutation(getUpdateRuleByIDMutationOptions(options));
};
/**
* This endpoint creates a new alert rule from the stored definition of an existing one. The clone keeps every modeled field of the source, including its enabled/disabled state, and gets the source's name suffixed with " - Copy" (or a bumped " - Copy (n)" counter when the source is itself a copy). The clone is owned by the caller.
* @summary Clone alert rule
*/
export const cloneRuleByID = (
{ id }: CloneRuleByIDPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CloneRuleByID201>({
url: `/api/v2/rules/${id}/clone`,
method: 'POST',
signal,
});
};
export const getCloneRuleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof cloneRuleByID>>,
TError,
{ pathParams: CloneRuleByIDPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof cloneRuleByID>>,
TError,
{ pathParams: CloneRuleByIDPathParameters },
TContext
> => {
const mutationKey = ['cloneRuleByID'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof cloneRuleByID>>,
{ pathParams: CloneRuleByIDPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return cloneRuleByID(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type CloneRuleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof cloneRuleByID>>
>;
export type CloneRuleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Clone alert rule
*/
export const useCloneRuleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof cloneRuleByID>>,
TError,
{ pathParams: CloneRuleByIDPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof cloneRuleByID>>,
TError,
{ pathParams: CloneRuleByIDPathParameters },
TContext
> => {
return useMutation(getCloneRuleByIDMutationOptions(options));
};
/**
* Returns distinct label keys from rule history entries for the selected range.
* @summary Get rule history filter keys

View File

@@ -431,8 +431,7 @@ export type AlertmanagertypesChannelEmailConfigDTOHeadersAnyOf = {
/**
* @nullable
*/
export type AlertmanagertypesChannelEmailConfigDTOHeaders =
AlertmanagertypesChannelEmailConfigDTOHeadersAnyOf | null;
export type AlertmanagertypesChannelEmailConfigDTOHeaders = AlertmanagertypesChannelEmailConfigDTOHeadersAnyOf | null;
export interface AlertmanagertypesChannelEmailConfigDTO {
/**
@@ -510,8 +509,7 @@ export type AlertmanagertypesChannelPagerdutyConfigDTODetailsAnyOf = {
/**
* @nullable
*/
export type AlertmanagertypesChannelPagerdutyConfigDTODetails =
AlertmanagertypesChannelPagerdutyConfigDTODetailsAnyOf | null;
export type AlertmanagertypesChannelPagerdutyConfigDTODetails = AlertmanagertypesChannelPagerdutyConfigDTODetailsAnyOf | null;
export interface AlertmanagertypesChannelPagerdutyConfigDTO {
/**
@@ -584,8 +582,7 @@ export type AlertmanagertypesChannelOpsgenieConfigDTODetailsAnyOf = {
/**
* @nullable
*/
export type AlertmanagertypesChannelOpsgenieConfigDTODetails =
AlertmanagertypesChannelOpsgenieConfigDTODetailsAnyOf | null;
export type AlertmanagertypesChannelOpsgenieConfigDTODetails = AlertmanagertypesChannelOpsgenieConfigDTODetailsAnyOf | null;
export interface AlertmanagertypesChannelOpsgenieConfigDTO {
/**
@@ -706,8 +703,7 @@ export type AlertmanagertypesChannelJiraConfigDTOCustomFieldsAnyOf = {
/**
* @nullable
*/
export type AlertmanagertypesChannelJiraConfigDTOCustomFields =
AlertmanagertypesChannelJiraConfigDTOCustomFieldsAnyOf | null;
export type AlertmanagertypesChannelJiraConfigDTOCustomFields = AlertmanagertypesChannelJiraConfigDTOCustomFieldsAnyOf | null;
export interface AlertmanagertypesChannelJiraConfigDTO {
/**
@@ -832,8 +828,7 @@ export type AlertmanagertypesChannelIncidentIOConfigDTOMetadataAnyOf = {
/**
* @nullable
*/
export type AlertmanagertypesChannelIncidentIOConfigDTOMetadata =
AlertmanagertypesChannelIncidentIOConfigDTOMetadataAnyOf | null;
export type AlertmanagertypesChannelIncidentIOConfigDTOMetadata = AlertmanagertypesChannelIncidentIOConfigDTOMetadataAnyOf | null;
export interface AlertmanagertypesChannelIncidentIOConfigDTO {
/**
@@ -872,6 +867,55 @@ export interface AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTy
spec: AlertmanagertypesChannelIncidentIOConfigDTO;
}
export enum AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTOKind {
telegram = 'telegram',
}
export interface AlertmanagertypesChannelTelegramConfigDTO {
/**
* @type string
*/
apiUrl?: string;
/**
* @type string
* @format password
*/
botToken: string;
/**
* @type integer
* @format int64
*/
chatId: number;
/**
* @type boolean
*/
disableNotifications?: boolean;
/**
* @type string
*/
message?: string;
/**
* @type integer
*/
messageThreadId?: number;
/**
* @type boolean,null
*/
sendResolved?: boolean | null;
/**
* @type string
*/
title?: string;
}
export interface AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTO {
/**
* @enum telegram
* @type string
*/
kind: AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTOKind;
spec: AlertmanagertypesChannelTelegramConfigDTO;
}
export type AlertmanagertypesChannelConfigDTO =
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelSlackConfigDTO
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelEmailConfigDTO
@@ -882,7 +926,8 @@ export type AlertmanagertypesChannelConfigDTO =
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelGoogleChatConfigDTO
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJiraConfigDTO
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJSMOpsConfigDTO
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelIncidentIOConfigDTO;
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelIncidentIOConfigDTO
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTO;
export enum AlertmanagertypesChannelDefectDTO {
none = 'none',
@@ -902,6 +947,7 @@ export enum AlertmanagertypesChannelKindDTO {
jira = 'jira',
jsmops = 'jsmops',
incidentio = 'incidentio',
telegram = 'telegram',
}
export enum AlertmanagertypesChannelListOrderDTO {
asc = 'asc',
@@ -2462,7 +2508,7 @@ export interface ConfigSNSConfigDTO {
topic_arn?: string;
}
export interface ConfigTelegramConfigDTO {
export interface AlertmanagertypesTelegramReceiverConfigDTO {
api_url?: ConfigURLType2DTO;
/**
* @type integer
@@ -2494,6 +2540,10 @@ export interface ConfigTelegramConfigDTO {
* @type boolean
*/
send_resolved?: boolean;
/**
* @type string
*/
title?: string;
/**
* @type string
*/
@@ -2699,7 +2749,7 @@ export type AlertmanagertypesPostableChannelDTO = unknown & {
/**
* @type array
*/
telegram_configs?: ConfigTelegramConfigDTO[];
telegram_configs?: AlertmanagertypesTelegramReceiverConfigDTO[];
/**
* @type array
*/
@@ -2846,7 +2896,7 @@ export interface AlertmanagertypesReceiverDTO {
/**
* @type array
*/
telegram_configs?: ConfigTelegramConfigDTO[];
telegram_configs?: AlertmanagertypesTelegramReceiverConfigDTO[];
/**
* @type array
*/
@@ -2899,6 +2949,9 @@ export interface AuthtypesAttributeMappingDTO {
role?: string;
}
/**
* The kind of authn provider.
*/
export enum AuthtypesAuthDomainConfigSAMLDTOKind {
saml = 'saml',
}
@@ -2926,11 +2979,18 @@ export interface AuthtypesAuthDomainConfigSAMLDTO {
/**
* @type string
* @enum saml
* @description The kind of authn provider.
*/
kind: AuthtypesAuthDomainConfigSAMLDTOKind;
/**
* @description The saml configuration.
*/
spec: AuthtypesSamlConfigDTO;
}
/**
* The kind of authn provider.
*/
export enum AuthtypesAuthDomainConfigGoogleDTOKind {
google = 'google',
}
@@ -2979,11 +3039,18 @@ export interface AuthtypesAuthDomainConfigGoogleDTO {
/**
* @type string
* @enum google
* @description The kind of authn provider.
*/
kind: AuthtypesAuthDomainConfigGoogleDTOKind;
/**
* @description The google auth configuration.
*/
spec: AuthtypesGoogleConfigDTO;
}
/**
* The kind of authn provider.
*/
export enum AuthtypesAuthDomainConfigOIDCDTOKind {
oidc = 'oidc',
}
@@ -3020,8 +3087,12 @@ export interface AuthtypesAuthDomainConfigOIDCDTO {
/**
* @type string
* @enum oidc
* @description The kind of authn provider.
*/
kind: AuthtypesAuthDomainConfigOIDCDTOKind;
/**
* @description The oidc configuration.
*/
spec: AuthtypesOIDCConfigDTO;
}
@@ -3080,8 +3151,7 @@ export type AuthtypesRoleMappingDTOGroupMappingsAnyOf = {
/**
* @nullable
*/
export type AuthtypesRoleMappingDTOGroupMappings =
AuthtypesRoleMappingDTOGroupMappingsAnyOf | null;
export type AuthtypesRoleMappingDTOGroupMappings = AuthtypesRoleMappingDTOGroupMappingsAnyOf | null;
export interface AuthtypesRoleMappingDTO {
/**
@@ -3692,8 +3762,7 @@ export type CloudintegrationtypesAgentReportDTODataAnyOf = {
/**
* @nullable
*/
export type CloudintegrationtypesAgentReportDTOData =
CloudintegrationtypesAgentReportDTODataAnyOf | null;
export type CloudintegrationtypesAgentReportDTOData = CloudintegrationtypesAgentReportDTODataAnyOf | null;
/**
* @nullable
@@ -3722,12 +3791,7 @@ export interface CloudintegrationtypesAzureAccountConfigDTO {
resourceGroups: string[];
}
export enum CloudintegrationtypesGCPConnectionMethodDTO {
manual = 'manual',
agent = 'agent',
}
export interface CloudintegrationtypesGCPAccountConfigDTO {
connectionMethod: CloudintegrationtypesGCPConnectionMethodDTO;
/**
* @type string
*/
@@ -3974,18 +4038,7 @@ export interface CloudintegrationtypesCollectedMetricDTO {
}
export interface CloudintegrationtypesGCPConnectionArtifactDTO {
/**
* @type string
*/
cliCommand: string;
/**
* @type string
*/
createSecretsCommand: string;
/**
* @type string
*/
createServiceAccountCommand: string;
[key: string]: unknown;
}
export interface CloudintegrationtypesConnectionArtifactDTO {
@@ -4024,56 +4077,8 @@ export interface CloudintegrationtypesDataCollectedDTO {
metrics?: CloudintegrationtypesCollectedMetricDTO[] | null;
}
export interface CloudintegrationtypesGCPLogsCollectionStrategyDTO {
/**
* @type string
*/
sinkFilter: string;
}
export interface CloudintegrationtypesGCPMetricsCollectionStrategyDTO {
/**
* @type array
*/
metricNames: string[];
}
export interface CloudintegrationtypesGCPTelemetryCollectionStrategyDTO {
logs?: CloudintegrationtypesGCPLogsCollectionStrategyDTO;
metrics?: CloudintegrationtypesGCPMetricsCollectionStrategyDTO;
}
export interface CloudintegrationtypesGCPIntegrationConfigDTO {
connectionMethod: CloudintegrationtypesGCPConnectionMethodDTO;
/**
* @type string
*/
deploymentProjectId: string;
/**
* @type string
*/
deploymentRegion: string;
/**
* @type array
*/
projectIds: string[];
telemetryCollectionStrategy: CloudintegrationtypesGCPTelemetryCollectionStrategyDTO;
}
export interface CloudintegrationtypesGCPPostableAccountConfigDTO {
connectionMethod: CloudintegrationtypesGCPConnectionMethodDTO;
/**
* @type string
*/
deploymentProjectId: string;
/**
* @type string
*/
deploymentRegion: string;
/**
* @type array
*/
projectIds: string[];
[key: string]: unknown;
}
export interface CloudintegrationtypesGettableAccountWithConnectionArtifactDTO {
@@ -4228,7 +4233,7 @@ export interface CloudintegrationtypesGettableServicesMetadataDTO {
export interface CloudintegrationtypesPostableAccountConfigDTO {
aws?: CloudintegrationtypesAWSPostableAccountConfigDTO;
azure?: CloudintegrationtypesAzureAccountConfigDTO;
gcp?: CloudintegrationtypesGCPPostableAccountConfigDTO;
gcp?: CloudintegrationtypesGCPAccountConfigDTO;
}
export interface CloudintegrationtypesPostableAccountDTO {
@@ -4243,8 +4248,7 @@ export type CloudintegrationtypesPostableAgentCheckInDTODataAnyOf = {
/**
* @nullable
*/
export type CloudintegrationtypesPostableAgentCheckInDTOData =
CloudintegrationtypesPostableAgentCheckInDTODataAnyOf | null;
export type CloudintegrationtypesPostableAgentCheckInDTOData = CloudintegrationtypesPostableAgentCheckInDTODataAnyOf | null;
export interface CloudintegrationtypesPostableAgentCheckInDTO {
/**
@@ -4372,9 +4376,9 @@ export interface CloudintegrationtypesUpdatableGCPAccountConfigDTO {
*/
deploymentRegion: string;
/**
* @type array
* @type array,null
*/
projectIds: string[];
projectIds: string[] | null;
}
export interface CloudintegrationtypesUpdatableAccountConfigDTO {
@@ -4486,6 +4490,48 @@ export interface ConfigJiraConfigDTO {
wont_fix_resolution?: string;
}
export interface ConfigTelegramConfigDTO {
api_url?: ConfigURLType2DTO;
/**
* @type integer
* @format int64
*/
chat?: number;
/**
* @type string
*/
chat_file?: string;
/**
* @type boolean
*/
disable_notifications?: boolean;
http_config?: ConfigHTTPClientConfigDTO;
/**
* @type string
*/
message?: string;
/**
* @type integer
*/
message_thread_id?: number;
/**
* @type string
*/
parse_mode?: string;
/**
* @type boolean
*/
send_resolved?: boolean;
/**
* @type string
*/
token?: string;
/**
* @type string
*/
token_file?: string;
}
export interface DashboardGridItemDTO {
content?: CommonJSONRefDTO;
/**
@@ -4547,6 +4593,9 @@ export interface Querybuildertypesv5TraceAggregationDTO {
expression?: string;
}
/**
* How the upper bounds are spaced.
*/
export enum Querybuildertypesv5BucketOptionsLinearDTOKind {
linear = 'linear',
}
@@ -4566,11 +4615,18 @@ export interface Querybuildertypesv5BucketOptionsLinearDTO {
/**
* @type string
* @enum linear
* @description How the upper bounds are spaced.
*/
kind: Querybuildertypesv5BucketOptionsLinearDTOKind;
/**
* @description The evenly spaced bucket specification.
*/
spec: Querybuildertypesv5LinearBucketsSpecDTO;
}
/**
* How the upper bounds are spaced.
*/
export enum Querybuildertypesv5BucketOptionsLogDTOKind {
log = 'log',
}
@@ -4585,8 +4641,12 @@ export interface Querybuildertypesv5BucketOptionsLogDTO {
/**
* @type string
* @enum log
* @description How the upper bounds are spaced.
*/
kind: Querybuildertypesv5BucketOptionsLogDTOKind;
/**
* @description The logarithmic bucket specification.
*/
spec: Querybuildertypesv5LogBucketsSpecDTO;
}
@@ -4932,8 +4992,7 @@ export type DashboardtypesLegendDTOCustomColorsAnyOf = {
/**
* @nullable
*/
export type DashboardtypesLegendDTOCustomColors =
DashboardtypesLegendDTOCustomColorsAnyOf | null;
export type DashboardtypesLegendDTOCustomColors = DashboardtypesLegendDTOCustomColorsAnyOf | null;
export interface DashboardtypesLegendDTO {
/**
@@ -5525,8 +5584,7 @@ export type DashboardtypesTableFormattingDTOColumnUnitsAnyOf = {
/**
* @nullable
*/
export type DashboardtypesTableFormattingDTOColumnUnits =
DashboardtypesTableFormattingDTOColumnUnitsAnyOf | null;
export type DashboardtypesTableFormattingDTOColumnUnits = DashboardtypesTableFormattingDTOColumnUnitsAnyOf | null;
export interface DashboardtypesTableFormattingDTO {
/**
@@ -5811,26 +5869,40 @@ export type Querybuildertypesv5BuilderQuerySpecDTO =
| Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregationDTO
| Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregationDTO;
/**
* The type of the query.
*/
export enum Querybuildertypesv5QueryEnvelopeBuilderDTOType {
builder_query = 'builder_query',
}
export interface Querybuildertypesv5QueryEnvelopeBuilderDTO {
/**
* @description The builder query specification.
*/
spec?: Querybuildertypesv5BuilderQuerySpecDTO;
/**
* @type string
* @enum builder_query
* @description The type of the query.
*/
type: Querybuildertypesv5QueryEnvelopeBuilderDTOType;
}
/**
* The type of the query.
*/
export enum Querybuildertypesv5QueryEnvelopeBuilderAIDTOType {
builder_ai_query = 'builder_ai_query',
}
export interface Querybuildertypesv5QueryEnvelopeBuilderAIDTO {
/**
* @description The AI builder query specification.
*/
spec?: Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregationDTO;
/**
* @type string
* @enum builder_ai_query
* @description The type of the query.
*/
type: Querybuildertypesv5QueryEnvelopeBuilderAIDTOType;
}
@@ -5868,14 +5940,21 @@ export interface Querybuildertypesv5QueryBuilderFormulaDTO {
order?: Querybuildertypesv5OrderByDTO[] | null;
}
/**
* The type of the query.
*/
export enum Querybuildertypesv5QueryEnvelopeFormulaDTOType {
builder_formula = 'builder_formula',
}
export interface Querybuildertypesv5QueryEnvelopeFormulaDTO {
/**
* @description The formula specification.
*/
spec?: Querybuildertypesv5QueryBuilderFormulaDTO;
/**
* @type string
* @enum builder_formula
* @description The type of the query.
*/
type: Querybuildertypesv5QueryEnvelopeFormulaDTOType;
}
@@ -5938,14 +6017,21 @@ export interface Querybuildertypesv5QueryBuilderTraceOperatorDTO {
stepInterval?: Querybuildertypesv5StepDTO;
}
/**
* The type of the query.
*/
export enum Querybuildertypesv5QueryEnvelopeTraceOperatorDTOType {
builder_trace_operator = 'builder_trace_operator',
}
export interface Querybuildertypesv5QueryEnvelopeTraceOperatorDTO {
/**
* @description The trace operator specification.
*/
spec?: Querybuildertypesv5QueryBuilderTraceOperatorDTO;
/**
* @type string
* @enum builder_trace_operator
* @description The type of the query.
*/
type: Querybuildertypesv5QueryEnvelopeTraceOperatorDTOType;
}
@@ -5974,14 +6060,21 @@ export interface Querybuildertypesv5PromQueryDTO {
step?: Querybuildertypesv5StepDTO;
}
/**
* The type of the query.
*/
export enum Querybuildertypesv5QueryEnvelopePromQLDTOType {
promql = 'promql',
}
export interface Querybuildertypesv5QueryEnvelopePromQLDTO {
/**
* @description The PromQL query specification.
*/
spec?: Querybuildertypesv5PromQueryDTO;
/**
* @type string
* @enum promql
* @description The type of the query.
*/
type: Querybuildertypesv5QueryEnvelopePromQLDTOType;
}
@@ -6005,14 +6098,21 @@ export interface Querybuildertypesv5ClickHouseQueryDTO {
query?: string;
}
/**
* The type of the query.
*/
export enum Querybuildertypesv5QueryEnvelopeClickHouseSQLDTOType {
clickhouse_sql = 'clickhouse_sql',
}
export interface Querybuildertypesv5QueryEnvelopeClickHouseSQLDTO {
/**
* @description The ClickHouse SQL query specification.
*/
spec?: Querybuildertypesv5ClickHouseQueryDTO;
/**
* @type string
* @enum clickhouse_sql
* @description The type of the query.
*/
type: Querybuildertypesv5QueryEnvelopeClickHouseSQLDTOType;
}
@@ -6841,8 +6941,7 @@ export type FeaturetypesGettableFeatureDTOVariantsAnyOf = {
/**
* @nullable
*/
export type FeaturetypesGettableFeatureDTOVariants =
FeaturetypesGettableFeatureDTOVariantsAnyOf | null;
export type FeaturetypesGettableFeatureDTOVariants = FeaturetypesGettableFeatureDTOVariantsAnyOf | null;
export interface FeaturetypesGettableFeatureDTO {
/**
@@ -7320,8 +7419,7 @@ export type InframonitoringtypesClusterRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesClusterRecordDTOMeta =
InframonitoringtypesClusterRecordDTOMetaAnyOf | null;
export type InframonitoringtypesClusterRecordDTOMeta = InframonitoringtypesClusterRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesNodeCountsByReadinessDTO {
/**
@@ -7594,8 +7692,7 @@ export type InframonitoringtypesContainerRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesContainerRecordDTOMeta =
InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
export type InframonitoringtypesContainerRecordDTOMeta = InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesContainerRecordDTO {
containerCountsByReady: InframonitoringtypesContainerCountsByReadyDTO;
@@ -7686,8 +7783,7 @@ export type InframonitoringtypesDaemonSetRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesDaemonSetRecordDTOMeta =
InframonitoringtypesDaemonSetRecordDTOMetaAnyOf | null;
export type InframonitoringtypesDaemonSetRecordDTOMeta = InframonitoringtypesDaemonSetRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesDaemonSetRecordDTO {
/**
@@ -7782,8 +7878,7 @@ export type InframonitoringtypesDeploymentRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesDeploymentRecordDTOMeta =
InframonitoringtypesDeploymentRecordDTOMetaAnyOf | null;
export type InframonitoringtypesDeploymentRecordDTOMeta = InframonitoringtypesDeploymentRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesDeploymentRecordDTO {
/**
@@ -7872,8 +7967,7 @@ export type InframonitoringtypesHostRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesHostRecordDTOMeta =
InframonitoringtypesHostRecordDTOMetaAnyOf | null;
export type InframonitoringtypesHostRecordDTOMeta = InframonitoringtypesHostRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesHostRecordDTO {
/**
@@ -7955,8 +8049,7 @@ export type InframonitoringtypesJobRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesJobRecordDTOMeta =
InframonitoringtypesJobRecordDTOMetaAnyOf | null;
export type InframonitoringtypesJobRecordDTOMeta = InframonitoringtypesJobRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesJobRecordDTO {
/**
@@ -8074,8 +8167,7 @@ export type InframonitoringtypesNamespaceRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesNamespaceRecordDTOMeta =
InframonitoringtypesNamespaceRecordDTOMetaAnyOf | null;
export type InframonitoringtypesNamespaceRecordDTOMeta = InframonitoringtypesNamespaceRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesNamespaceRecordDTO {
/**
@@ -8142,8 +8234,7 @@ export type InframonitoringtypesNodeRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesNodeRecordDTOMeta =
InframonitoringtypesNodeRecordDTOMetaAnyOf | null;
export type InframonitoringtypesNodeRecordDTOMeta = InframonitoringtypesNodeRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesNodeRecordDTO {
condition: InframonitoringtypesNodeConditionDTO;
@@ -8214,8 +8305,7 @@ export type InframonitoringtypesPodRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesPodRecordDTOMeta =
InframonitoringtypesPodRecordDTOMetaAnyOf | null;
export type InframonitoringtypesPodRecordDTOMeta = InframonitoringtypesPodRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesPodRecordDTO {
/**
@@ -8602,8 +8692,7 @@ export type InframonitoringtypesStatefulSetRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesStatefulSetRecordDTOMeta =
InframonitoringtypesStatefulSetRecordDTOMetaAnyOf | null;
export type InframonitoringtypesStatefulSetRecordDTOMeta = InframonitoringtypesStatefulSetRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesStatefulSetRecordDTO {
/**
@@ -8679,8 +8768,7 @@ export type InframonitoringtypesVolumeRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesVolumeRecordDTOMeta =
InframonitoringtypesVolumeRecordDTOMetaAnyOf | null;
export type InframonitoringtypesVolumeRecordDTOMeta = InframonitoringtypesVolumeRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesVolumeRecordDTO {
/**
@@ -9798,8 +9886,7 @@ export type PrometheusMatrixSeriesSchemaDTOMetricAnyOf = {
/**
* @nullable
*/
export type PrometheusMatrixSeriesSchemaDTOMetric =
PrometheusMatrixSeriesSchemaDTOMetricAnyOf | null;
export type PrometheusMatrixSeriesSchemaDTOMetric = PrometheusMatrixSeriesSchemaDTOMetricAnyOf | null;
export interface PrometheusMatrixSeriesSchemaDTO {
/**
@@ -9831,8 +9918,7 @@ export type PrometheusVectorSampleSchemaDTOMetricAnyOf = {
/**
* @nullable
*/
export type PrometheusVectorSampleSchemaDTOMetric =
PrometheusVectorSampleSchemaDTOMetricAnyOf | null;
export type PrometheusVectorSampleSchemaDTOMetric = PrometheusVectorSampleSchemaDTOMetricAnyOf | null;
export interface PrometheusVectorSampleSchemaDTO {
/**
@@ -9942,6 +10028,28 @@ export interface PromotetypesPromotePathDTO {
signal: string;
}
export interface PromotetypesIndexMaterializedPathsResultDTO {
/**
* @type array,null
*/
indexed: PromotetypesPromotePathDTO[] | null;
/**
* @type array,null
*/
skipped: PromotetypesPromotePathDTO[] | null;
}
export interface PromotetypesPostableIndexMaterializedPathsDTO {
/**
* @type boolean
*/
dryRun?: boolean;
/**
* @type string
*/
signal: string;
}
export interface Querybuildertypesv5AggregationMetaDTO {
/**
* @type array
@@ -10220,8 +10328,7 @@ export type Querybuildertypesv5RawRowDTODataAnyOf = { [key: string]: unknown };
/**
* @nullable
*/
export type Querybuildertypesv5RawRowDTOData =
Querybuildertypesv5RawRowDTODataAnyOf | null;
export type Querybuildertypesv5RawRowDTOData = Querybuildertypesv5RawRowDTODataAnyOf | null;
export interface Querybuildertypesv5RawRowDTO {
/**
@@ -10292,8 +10399,7 @@ export type Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQueryAnyOf =
/**
* @nullable
*/
export type Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQuery =
Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQueryAnyOf | null;
export type Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQuery = Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQueryAnyOf | null;
/**
* Response from the v5 query range preview (dry-run) endpoint. For each query in the composite query, returns the underlying ClickHouse statement(s) it renders to without executing them (one per PromQL metric selector; exactly one for builder/ClickHouse/trace-operator queries), with the optional EXPLAIN ESTIMATE and granule analysis attached per statement when requested.
@@ -10693,6 +10799,9 @@ export interface RuletypesCumulativeWindowDTO {
timezone: string;
}
/**
* The kind of evaluation.
*/
export enum RuletypesEvaluationCumulativeDTOKind {
cumulative = 'cumulative',
}
@@ -10700,11 +10809,18 @@ export interface RuletypesEvaluationCumulativeDTO {
/**
* @type string
* @enum cumulative
* @description The kind of evaluation.
*/
kind: RuletypesEvaluationCumulativeDTOKind;
/**
* @description The cumulative window evaluation specification.
*/
spec: RuletypesCumulativeWindowDTO;
}
/**
* The kind of evaluation.
*/
export enum RuletypesEvaluationRollingDTOKind {
rolling = 'rolling',
}
@@ -10723,8 +10839,12 @@ export interface RuletypesEvaluationRollingDTO {
/**
* @type string
* @enum rolling
* @description The kind of evaluation.
*/
kind: RuletypesEvaluationRollingDTOKind;
/**
* @description The rolling window evaluation specification.
*/
spec: RuletypesRollingWindowDTO;
}
@@ -10930,16 +11050,28 @@ export enum RuletypesSeasonalityDTO {
daily = 'daily',
weekly = 'weekly',
}
/**
* The kind of threshold.
*/
export enum RuletypesThresholdBasicDTOKind {
basic = 'basic',
}
/**
* The basic threshold specification (array of thresholds).
*/
export type RuletypesThresholdBasicDTOSpec = RuletypesBasicRuleThresholdsDTO | null;
export interface RuletypesThresholdBasicDTO {
/**
* @type string
* @enum basic
* @description The kind of threshold.
*/
kind: RuletypesThresholdBasicDTOKind;
spec: RuletypesBasicRuleThresholdsDTO | null;
/**
* @description The basic threshold specification (array of thresholds).
*/
spec: RuletypesThresholdBasicDTOSpec;
}
export type RuletypesRuleThresholdDataDTO = RuletypesThresholdBasicDTO;
@@ -11602,8 +11734,7 @@ export type SpantypesSpanMapperTestSpanDTOAttributesAnyOf = {
/**
* @nullable
*/
export type SpantypesSpanMapperTestSpanDTOAttributes =
SpantypesSpanMapperTestSpanDTOAttributesAnyOf | null;
export type SpantypesSpanMapperTestSpanDTOAttributes = SpantypesSpanMapperTestSpanDTOAttributesAnyOf | null;
export type SpantypesSpanMapperTestSpanDTOResourceAnyOf = {
[key: string]: unknown;
@@ -11612,8 +11743,7 @@ export type SpantypesSpanMapperTestSpanDTOResourceAnyOf = {
/**
* @nullable
*/
export type SpantypesSpanMapperTestSpanDTOResource =
SpantypesSpanMapperTestSpanDTOResourceAnyOf | null;
export type SpantypesSpanMapperTestSpanDTOResource = SpantypesSpanMapperTestSpanDTOResourceAnyOf | null;
export interface SpantypesSpanMapperTestSpanDTO {
/**
@@ -11725,8 +11855,7 @@ export type SpantypesSpanAggregationResultDTOValueAnyOf = {
/**
* @nullable
*/
export type SpantypesSpanAggregationResultDTOValue =
SpantypesSpanAggregationResultDTOValueAnyOf | null;
export type SpantypesSpanAggregationResultDTOValue = SpantypesSpanAggregationResultDTOValueAnyOf | null;
export interface SpantypesSpanAggregationResultDTO {
aggregation: SpantypesSpanAggregationTypeDTO;
@@ -11853,16 +11982,12 @@ export interface SpantypesThreadSpanDTO {
* @type array
*/
events: SpantypesEventDTO[];
formatted_input: AiobservabilitytypesInputMessagesDTO;
formatted_output: AiobservabilitytypesOutputMessagesDTO;
/**
* @type string
*/
formatter: string;
formattedInput: AiobservabilitytypesInputMessagesDTO;
formattedOutput: AiobservabilitytypesOutputMessagesDTO;
/**
* @type array
*/
formatter_warnings: string[];
formatterWarnings: string[];
/**
* @type boolean
*/
@@ -11932,16 +12057,14 @@ export type SpantypesWaterfallSpanDTOAttributesAnyOf = {
/**
* @nullable
*/
export type SpantypesWaterfallSpanDTOAttributes =
SpantypesWaterfallSpanDTOAttributesAnyOf | null;
export type SpantypesWaterfallSpanDTOAttributes = SpantypesWaterfallSpanDTOAttributesAnyOf | null;
export type SpantypesWaterfallSpanDTOResourceAnyOf = { [key: string]: string };
/**
* @nullable
*/
export type SpantypesWaterfallSpanDTOResource =
SpantypesWaterfallSpanDTOResourceAnyOf | null;
export type SpantypesWaterfallSpanDTOResource = SpantypesWaterfallSpanDTOResourceAnyOf | null;
export interface SpantypesWaterfallSpanDTO {
/**
@@ -11978,6 +12101,12 @@ export interface SpantypesWaterfallSpanDTO {
* @minimum 0
*/
flags?: number;
formattedInput?: AiobservabilitytypesInputMessagesDTO;
formattedOutput?: AiobservabilitytypesOutputMessagesDTO;
/**
* @type array
*/
formatterWarnings?: string[];
/**
* @type boolean
*/
@@ -12375,8 +12504,7 @@ export type TelemetrytypesGettableFieldKeysDTOKeysAnyOf = {
/**
* @nullable
*/
export type TelemetrytypesGettableFieldKeysDTOKeys =
TelemetrytypesGettableFieldKeysDTOKeysAnyOf | null;
export type TelemetrytypesGettableFieldKeysDTOKeys = TelemetrytypesGettableFieldKeysDTOKeysAnyOf | null;
export interface TelemetrytypesGettableFieldKeysDTO {
/**
@@ -13312,6 +13440,14 @@ export type ListPromotedPaths200 = {
status: string;
};
export type IndexMaterializedPaths200 = {
data: PromotetypesIndexMaterializedPathsResultDTO;
/**
* @type string
*/
status: string;
};
export type ListRoles200 = {
/**
* @type array
@@ -14644,6 +14780,17 @@ export type PatchRuleByID200 = {
export type UpdateRuleByIDPathParameters = {
id: string;
};
export type CloneRuleByIDPathParameters = {
id: string;
};
export type CloneRuleByID201 = {
data: RuletypesRuleDTO;
/**
* @type string
*/
status: string;
};
export type GetRuleHistoryFilterKeysPathParameters = {
id: string;
};

View File

@@ -261,7 +261,7 @@ export const invalidateGetTraceSummary = async (
};
/**
* Returns the spans carrying gen_ai input or output messages, or a tool execution, in timestamp order. Messages already in the OTel GenAI shape are decoded into formatted_input and formatted_output, with formatter naming the converter and formatter_warnings what it could not resolve. Pass nextCursor as after or prevCursor as before to page, or spanId to open the page around a span.
* Returns the gen_ai spans of a trace, model calls, tool calls and agent spans, in timestamp order. The messages a span carries are decoded into formattedInput and formattedOutput, with formatterWarnings holding what the converter could not resolve; a span without messages has both empty. Pass nextCursor as after or prevCursor as before to page, or spanId to open the page around a span.
* @summary Get thread view for a trace
*/
export const getTraceThread = (
@@ -475,7 +475,7 @@ export const useGetFlamegraph = <
return useMutation(getGetFlamegraphMutationOptions(options));
};
/**
* Returns the waterfall view of spans including all spans if total spans are under a limit, a max count otherwise. Aggregations are dropped compared to v3
* Returns the waterfall view of spans including all spans if total spans are under a limit, a max count otherwise. Aggregations are dropped compared to v3. A span carrying gen_ai input or output messages also has them decoded into formattedInput and formattedOutput, with formatterWarnings holding what the converter could not resolve.
* @summary Get waterfall view for a trace
*/
export const getWaterfallV4 = (

View File

@@ -61,7 +61,11 @@ describe('convertV5ResponseToLegacy', () => {
],
values: [
{ timestamp: 1000, value: 10 } as unknown as TimeSeriesValue,
{ timestamp: 2000, value: 12 } as unknown as TimeSeriesValue,
{
timestamp: 2000,
value: 12,
partial: true,
} as unknown as TimeSeriesValue,
],
} as unknown as TimeSeries,
],
@@ -104,7 +108,7 @@ describe('convertV5ResponseToLegacy', () => {
labels: { 'service.name': 'adservice' },
values: [
{ timestamp: 1000, value: '10' },
{ timestamp: 2000, value: '12' },
{ timestamp: 2000, value: '12', partial: true },
],
metaData: expect.objectContaining({
alias: '__result_0',

View File

@@ -125,6 +125,7 @@ function convertTimeSeriesData(
values: series.values.map((value: any) => ({
timestamp: value.timestamp,
value: String(value.value),
...(value.partial ? { partial: true } : {}),
})),
metaData: {
alias,

View File

@@ -165,17 +165,15 @@ function createBaseSpec(
filter: getFilter(queryData),
groupBy:
queryData.groupBy?.length > 0
? queryData.groupBy.map(
(item: any): GroupByKey => ({
name: item.key,
fieldDataType: item?.dataType || '',
fieldContext: item?.type || '',
description: item?.description,
unit: item?.unit,
signal: item?.signal,
materialized: item?.materialized,
}),
)
? queryData.groupBy.map((item: any): GroupByKey => ({
name: item.key,
fieldDataType: item?.dataType || '',
fieldContext: item?.type || '',
description: item?.description,
unit: item?.unit,
signal: item?.signal,
materialized: item?.materialized,
}))
: undefined,
limit:
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
@@ -187,14 +185,12 @@ function createBaseSpec(
: undefined,
order:
queryData.orderBy?.length > 0
? queryData.orderBy.map(
(order: any): OrderBy => ({
key: {
name: order.columnName,
},
direction: order.order,
}),
)
? queryData.orderBy.map((order: any): OrderBy => ({
key: {
name: order.columnName,
},
direction: order.order,
}))
: undefined,
legend: isEmpty(queryData.legend) ? undefined : queryData.legend,
having: normalizeHaving(queryData.having),
@@ -397,17 +393,15 @@ function createTraceOperatorBaseSpec(
stepInterval: stepInterval || undefined,
groupBy:
groupBy?.length > 0
? groupBy.map(
(item: any): GroupByKey => ({
name: item.key,
fieldDataType: item?.dataType,
fieldContext: item?.type,
description: item?.description,
unit: item?.unit,
signal: item?.signal,
materialized: item?.materialized,
}),
)
? groupBy.map((item: any): GroupByKey => ({
name: item.key,
fieldDataType: item?.dataType,
fieldContext: item?.type,
description: item?.description,
unit: item?.unit,
signal: item?.signal,
materialized: item?.materialized,
}))
: undefined,
limit:
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
@@ -416,28 +410,24 @@ function createTraceOperatorBaseSpec(
offset: requestType === 'raw' || requestType === 'trace' ? offset : undefined,
order:
orderBy?.length > 0
? orderBy.map(
(order: any): OrderBy => ({
key: {
name: order.columnName,
},
direction: order.order,
}),
)
? orderBy.map((order: any): OrderBy => ({
key: {
name: order.columnName,
},
direction: order.order,
}))
: undefined,
legend: isEmpty(legend) ? undefined : legend,
having: normalizeHaving(having),
selectFields: isEmpty(nonEmptySelectColumns)
? undefined
: nonEmptySelectColumns?.map(
(column: any): TelemetryFieldKey => ({
name: column.name ?? column.key,
fieldDataType:
column?.fieldDataType ?? (column?.dataType as FieldDataType),
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
signal: column?.signal ?? undefined,
}),
),
: nonEmptySelectColumns?.map((column: any): TelemetryFieldKey => ({
name: column.name ?? column.key,
fieldDataType:
column?.fieldDataType ?? (column?.dataType as FieldDataType),
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
signal: column?.signal ?? undefined,
})),
};
}
@@ -610,14 +600,12 @@ export const prepareQueryRangePayloadV5 = (
disabled: formulaData.disabled,
limit: formulaData.limit ?? undefined,
legend: isEmpty(formulaData.legend) ? undefined : formulaData.legend,
order: formulaData.orderBy?.map(
(order: any): OrderBy => ({
key: {
name: order.columnName,
},
direction: order.order,
}),
),
order: formulaData.orderBy?.map((order: any): OrderBy => ({
key: {
name: order.columnName,
},
direction: order.order,
})),
},
}),
);

View File

@@ -1,3 +1,3 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#29F1FB" d="M8.932 20.806c-.369 0-.738.007-1.109 0-.35-.007-.587-.206-.623-.5a.587.587 0 0 1 .53-.636c.79-.062 1.582-.063 2.372-.003a.548.548 0 0 1 .522.602c-.024.326-.253.526-.616.54zM1.792 8.345c-.392 0-.782.008-1.173.002-.327-.006-.577-.22-.614-.512-.037-.293.146-.544.499-.615.192-.032.388-.045.583-.039a81.515 81.515 0 0 1 1.597 0c.163 0 .325.019.483.056.288.073.445.318.411.617-.034.298-.214.477-.515.487-.424.014-.848.004-1.272.004zm7.588 8.417H4.292a2.464 2.464 0 0 1-.326-.007c-.294-.04-.48-.209-.508-.506-.029-.298.11-.501.391-.606.179-.065.365-.051.549-.051 3.347 0 6.695.005 10.042-.006 1.174-.004 2.187-.439 2.993-1.3.69-.738 1.053-1.63 1.16-2.635.085-.788-.027-1.513-.516-2.156-.544-.718-1.28-1.078-2.163-1.082-3.163-.013-6.328-.005-9.487-.01-.336 0-.673-.027-1.007-.058-.29-.027-.45-.201-.469-.492-.021-.317.141-.545.429-.6a1.55 1.55 0 0 1 .29-.015h10.177c1.71.004 3.187 1.038 3.726 2.654.383 1.147.246 2.304-.182 3.416-.824 2.135-2.762 3.448-5.055 3.454-1.652.005-3.304 0-4.956 0zm2.906-13.568c1.533 0 3.066-.008 4.598 0 2.935.018 5.629 1.892 6.653 4.626.442 1.181.538 2.403.412 3.657-.185 1.842-.735 3.552-1.776 5.084-1.608 2.365-3.873 3.68-6.679 4.118-.95.148-1.905.13-2.86.13-.397 0-.61-.181-.633-.51-.025-.351.196-.621.587-.645.434-.026.87-.004 1.305-.016 2.641-.072 4.928-.982 6.74-2.935 1.269-1.37 1.912-3.039 2.13-4.878.151-1.275.135-2.544-.37-3.752-.773-1.85-2.159-2.983-4.068-3.509-.74-.204-1.5-.243-2.26-.247-2.837-.017-5.675-.007-8.511-.007-.12 0-.24.004-.359-.006a.57.57 0 0 1-.517-.536.557.557 0 0 1 .456-.557c.13-.018.261-.024.392-.019h4.762Z"/>
<path fill="#0E9AA7" d="M8.932 20.806c-.369 0-.738.007-1.109 0-.35-.007-.587-.206-.623-.5a.587.587 0 0 1 .53-.636c.79-.062 1.582-.063 2.372-.003a.548.548 0 0 1 .522.602c-.024.326-.253.526-.616.54zM1.792 8.345c-.392 0-.782.008-1.173.002-.327-.006-.577-.22-.614-.512-.037-.293.146-.544.499-.615.192-.032.388-.045.583-.039a81.515 81.515 0 0 1 1.597 0c.163 0 .325.019.483.056.288.073.445.318.411.617-.034.298-.214.477-.515.487-.424.014-.848.004-1.272.004zm7.588 8.417H4.292a2.464 2.464 0 0 1-.326-.007c-.294-.04-.48-.209-.508-.506-.029-.298.11-.501.391-.606.179-.065.365-.051.549-.051 3.347 0 6.695.005 10.042-.006 1.174-.004 2.187-.439 2.993-1.3.69-.738 1.053-1.63 1.16-2.635.085-.788-.027-1.513-.516-2.156-.544-.718-1.28-1.078-2.163-1.082-3.163-.013-6.328-.005-9.487-.01-.336 0-.673-.027-1.007-.058-.29-.027-.45-.201-.469-.492-.021-.317.141-.545.429-.6a1.55 1.55 0 0 1 .29-.015h10.177c1.71.004 3.187 1.038 3.726 2.654.383 1.147.246 2.304-.182 3.416-.824 2.135-2.762 3.448-5.055 3.454-1.652.005-3.304 0-4.956 0zm2.906-13.568c1.533 0 3.066-.008 4.598 0 2.935.018 5.629 1.892 6.653 4.626.442 1.181.538 2.403.412 3.657-.185 1.842-.735 3.552-1.776 5.084-1.608 2.365-3.873 3.68-6.679 4.118-.95.148-1.905.13-2.86.13-.397 0-.61-.181-.633-.51-.025-.351.196-.621.587-.645.434-.026.87-.004 1.305-.016 2.641-.072 4.928-.982 6.74-2.935 1.269-1.37 1.912-3.039 2.13-4.878.151-1.275.135-2.544-.37-3.752-.773-1.85-2.159-2.983-4.068-3.509-.74-.204-1.5-.243-2.26-.247-2.837-.017-5.675-.007-8.511-.007-.12 0-.24.004-.359-.006a.57.57 0 0 1-.517-.536.557.557 0 0 1 .456-.557c.13-.018.261-.024.392-.019h4.762Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,3 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#9CA3AF" height="34" viewBox="0 0 131 34">
<path fill="#9CA3AF" d="M.36 8.6h16.7v5.6H6.04c-.2 0-.35.16-.35.35v4.9c0 .2.16.35.35.35h11.02v5.6h-5.33c-.2 0-.35.16-.35.35v4.9c0 .2.16.35.35.35h4.98c.2 0 .35-.15.35-.35V25.4h5.34c.2 0 .35-.16.35-.35v-4.9c0-.2-.16-.35-.35-.35h-5.34v-5.6h5.34c.2 0 .35-.16.35-.35v-4.9c0-.2-.16-.35-.35-.35h-5.34V3.35c0-.2-.16-.35-.35-.35H.36c-.2 0-.36.16-.36.35v4.9c0 .2.16.35.36.35ZM44.41 14.7c-.5-.5-1.1-.9-1.76-1.18a5.62 5.62 0 0 0-4.6.17c-.73.37-1.32.91-1.75 1.62h-.17V8.59H34.1v16.83h2.04v-1.81h.17c.21.36.47.67.77.94.31.25.65.48 1.01.67.37.18.77.31 1.18.39a6.2 6.2 0 0 0 3.39-.24 5.36 5.36 0 0 0 3.02-3.1c.29-.75.44-1.62.44-2.6v-.47c0-.96-.16-1.83-.47-2.58-.3-.75-.7-1.4-1.23-1.9v-.01Zm-5.87.66a3.9 3.9 0 0 1 4.34.84c.36.35.64.8.83 1.3.2.5.3 1.07.3 1.7v.47c0 .64-.1 1.23-.3 1.74a3.75 3.75 0 0 1-2.06 2.15 4.27 4.27 0 0 1-3.12-.03 3.86 3.86 0 0 1-2.09-2.2c-.2-.52-.3-1.11-.3-1.75v-.29c0-.62.1-1.2.3-1.7v-.01c.21-.53.5-.99.84-1.36.36-.37.78-.66 1.26-.86ZM97.04 8.59H95v4.86h-2.94v1.86H95v8.17c0 .56.17 1.03.53 1.4.37.35.84.54 1.4.54h4.18v-1.87h-3.5c-.2 0-.33-.05-.43-.15-.1-.1-.14-.27-.14-.49v-7.6h4.65v-1.86h-4.65V8.59ZM114.61 15a5.48 5.48 0 0 0-1.8-1.33 5.6 5.6 0 0 0-2.57-.56 6.17 6.17 0 0 0-4.26 1.7 5.6 5.6 0 0 0-1.72 4.2v.57c0 .9.15 1.75.44 2.5a5.58 5.58 0 0 0 5.5 3.67c1.55 0 2.8-.35 3.72-1.04a5.35 5.35 0 0 0 1.91-2.73l.03-.07-1.94-.52-.02.07c-.11.33-.27.64-.46.94-.17.27-.4.52-.7.74-.28.22-.63.39-1.04.51-.41.13-.9.19-1.46.19a3.8 3.8 0 0 1-2.84-1.05 4.07 4.07 0 0 1-1.1-2.7h9.68v-1.6c0-.54-.11-1.12-.34-1.75a5.04 5.04 0 0 0-1.03-1.74Zm-8.25 3.21a3.8 3.8 0 0 1 1.22-2.25 4.19 4.19 0 0 1 3.99-.7c.44.16.83.38 1.17.66.34.27.62.62.82 1.02.21.38.34.8.38 1.27h-7.58ZM129.09 14.42a4.47 4.47 0 0 0-3.37-1.3c-.93 0-1.73.2-2.4.59-.64.39-1.15.97-1.52 1.74h-.17v-2h-2.04v11.97h2.04v-6.23c0-1.26.32-2.28.95-3.02a3.31 3.31 0 0 1 2.65-1.14c.94 0 1.7.3 2.24.9.56.6.83 1.52.83 2.74v6.75h2.04v-7.13c0-1.71-.42-3.02-1.25-3.87ZM88.1 15a5.48 5.48 0 0 0-1.78-1.33 5.6 5.6 0 0 0-2.58-.56 6.17 6.17 0 0 0-4.27 1.7 5.59 5.59 0 0 0-1.71 4.2v.56c0 .92.14 1.76.44 2.51a5.6 5.6 0 0 0 5.5 3.67c1.55 0 2.8-.35 3.72-1.04a5.36 5.36 0 0 0 1.91-2.73l.03-.07-1.94-.52-.03.07c-.1.32-.26.64-.45.94-.17.27-.4.52-.7.74-.29.21-.64.39-1.05.51-.4.12-.9.19-1.45.19a3.8 3.8 0 0 1-2.85-1.05 4.07 4.07 0 0 1-1.09-2.7h9.68v-1.61c0-.53-.12-1.12-.34-1.74A5.03 5.03 0 0 0 88.1 15Zm-8.24 3.21a3.83 3.83 0 0 1 1.22-2.25 4.2 4.2 0 0 1 3.99-.7c.44.16.83.38 1.16.66.35.27.62.62.83 1.02.2.38.33.8.37 1.27h-7.57ZM73.65 19.42a6.11 6.11 0 0 0-3.23-1.02 6.63 6.63 0 0 1-2.68-.58c-.47-.3-.7-.7-.7-1.25 0-.27.08-.5.21-.7.14-.2.33-.38.56-.52a4.05 4.05 0 0 1 1.78-.42c.85 0 1.54.21 2.06.63.53.41.83 1 .91 1.73l.01.1 1.95-.47-.01-.07c-.07-.45-.22-.91-.45-1.36a3.46 3.46 0 0 0-.94-1.2 4.6 4.6 0 0 0-1.52-.84 6.05 6.05 0 0 0-2.1-.34c-.58 0-1.13.07-1.65.22-.52.14-1 .36-1.43.65-.41.3-.74.66-.99 1.1a2.9 2.9 0 0 0-.37 1.49v.14c0 1.06.38 1.87 1.14 2.42a6.2 6.2 0 0 0 3.24.97c1.18.08 2.04.26 2.56.54.5.28.75.72.75 1.36 0 .6-.25 1.06-.78 1.4-.52.32-1.22.48-2.1.48a3.68 3.68 0 0 1-2.46-.79 3.13 3.13 0 0 1-1.04-2.14v-.09l-1.93.46h-.02v.07a4.4 4.4 0 0 0 3.1 4c.7.24 1.52.36 2.46.36.7 0 1.35-.09 1.93-.27.6-.16 1.12-.4 1.53-.72A3.38 3.38 0 0 0 74.8 22v-.14c0-1.07-.39-1.9-1.14-2.44ZM60.25 23.4c-.1-.1-.14-.27-.14-.49v-9.46h-2.05v1.85h-.16a3.78 3.78 0 0 0-1.61-1.63 4.62 4.62 0 0 0-2.26-.56c-.77 0-1.5.14-2.19.41a5.27 5.27 0 0 0-3.02 3.12c-.29.75-.44 1.63-.44 2.6v.38c0 .99.15 1.87.44 2.63.3.75.7 1.4 1.2 1.93a5.48 5.48 0 0 0 4.05 1.57c.8 0 1.51-.2 2.2-.58.69-.38 1.24-.97 1.63-1.75h.16v.06c0 .56.18 1.03.53 1.4.37.35.85.54 1.41.54h1.36v-1.87h-.68c-.2 0-.34-.05-.43-.15Zm-4.46.13c-.46.2-.97.3-1.52.3a3.68 3.68 0 0 1-2.75-1.09 4.42 4.42 0 0 1-1.05-3.12v-.38c0-.62.1-1.2.29-1.71a3.65 3.65 0 0 1 5-2.17c.47.2.88.49 1.21.86.35.37.62.83.8 1.36.2.5.3 1.08.3 1.7v.3c0 .63-.1 1.23-.3 1.76-.18.5-.45.96-.78 1.33-.33.37-.73.66-1.2.86Z"/>
</svg>
<svg role="img" viewBox="-2.625 3 28 28" xmlns="http://www.w3.org/2000/svg"><path fill="#7A8291" d="M.36 8.6h16.7v5.6H6.04c-.2 0-.35.16-.35.35v4.9c0 .2.16.35.35.35h11.02v5.6h-5.33c-.2 0-.35.16-.35.35v4.9c0 .2.16.35.35.35h4.98c.2 0 .35-.15.35-.35V25.4h5.34c.2 0 .35-.16.35-.35v-4.9c0-.2-.16-.35-.35-.35h-5.34v-5.6h5.34c.2 0 .35-.16.35-.35v-4.9c0-.2-.16-.35-.35-.35h-5.34V3.35c0-.2-.16-.35-.35-.35H.36c-.2 0-.36.16-.36.35v4.9c0 .2.16.35.36.35Z"/></svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" id="图层_1" x="0" y="0" version="1.1" viewBox="0 0 519.27 214.14"><style>.st2{fill:#a9cc54}</style><path d="M493.54 171.25c-35.63 0-64.52-28.89-64.52-64.52s28.89-64.52 64.52-64.52c3.85 0 7.62.36 11.29 1L493.54 5.45H104.46v.02C49.24 6.51 4.78 51.59 4.78 107.07c0 55.47 44.45 100.56 99.68 101.59v.02h389.07l11.29-38.44c-3.66.65-7.43 1.01-11.28 1.01" style="fill:#b6de64"/><defs><path id="SVGID_1_" d="M493.54 171.25c-35.63 0-64.52-28.89-64.52-64.52s28.89-64.52 64.52-64.52c3.85 0 7.62.36 11.29 1V5.45H104.46v.02C49.24 6.51 4.78 51.59 4.78 107.07c0 55.47 44.45 100.56 99.68 101.59v.02h389.07l11.29-38.44c-3.66.65-7.43 1.01-11.28 1.01"/></defs><clipPath id="SVGID_2_"><use xlink:href="#SVGID_1_" style="overflow:visible"/></clipPath><g style="clip-path:url(#SVGID_2_)"><path d="M-23.16 55.58H428.5v51.1H-23.16zM-23.16 157.59h530.98v72.35H-23.16z" class="st2"/></g><path d="M493.54 184.23c-43.94 0-79.56-33.62-79.56-77.56s35.62-76.56 79.56-76.56" style="fill:none;stroke:#ddf4a4;stroke-width:49;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"/></svg>
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#37814A" d="M2.303 0A2.298 2.298 0 0 0 0 2.303v19.394A2.298 2.298 0 0 0 2.303 24h19.394A2.298 2.298 0 0 0 24 21.697V2.303A2.298 2.298 0 0 0 21.697 0zm8.177 3.072c4.098 0 7.028 1.438 7.68 1.764l-1.194 2.55c-2.442-1.057-4.993-1.41-5.672-1.41-1.574 0-2.17.922-2.17 1.763v8.494c0 .869.596 1.791 2.17 1.791.679 0 3.23-.38 5.672-1.41l1.194 2.496c-.435.271-3.637 1.818-7.68 1.818-1.112 0-4.64-.244-4.64-4.64V7.713c0-4.397 3.528-4.64 4.64-4.64z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 529 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,5 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="lucide/github">
<path id="Vector" d="M15 22V18C15.1391 16.7473 14.7799 15.4901 14 14.5C17 14.5 20 12.5 20 9C20.08 7.75 19.73 6.52 19 5.5C19.28 4.35 19.28 3.15 19 2C19 2 18 2 16 3.5C13.36 3 10.64 3 8 3.5C6 2 5 2 5 2C4.7 3.15 4.7 4.35 5 5.5C4.27187 6.51588 3.91847 7.75279 4 9C4 12.5 7 14.5 10 14.5C9.61 14.99 9.32 15.55 9.15 16.15C8.98 16.75 8.93 17.38 9 18M9 18V22M9 18C4.49 20 4 16 2 16" stroke="#C0C1C3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector" d="M15 22V18C15.1391 16.7473 14.7799 15.4901 14 14.5C17 14.5 20 12.5 20 9C20.08 7.75 19.73 6.52 19 5.5C19.28 4.35 19.28 3.15 19 2C19 2 18 2 16 3.5C13.36 3 10.64 3 8 3.5C6 2 5 2 5 2C4.7 3.15 4.7 4.35 5 5.5C4.27187 6.51588 3.91847 7.75279 4 9C4 12.5 7 14.5 10 14.5C9.61 14.99 9.32 15.55 9.15 16.15C8.98 16.75 8.93 17.38 9 18M9 18V22M9 18C4.49 20 4 16 2 16" stroke="#7A8291" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 587 B

After

Width:  |  Height:  |  Size: 587 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#FFEC6E" d="m0 0 11.955 24L24 0zm13.366 4.827h1.393v1.38h-1.393zm-2.77 5.569H9.22V8.993h1.389zm0-2.087H9.22V6.906h1.389zm0-2.086H9.22V4.819h1.389zm2.087 6.263h-1.377V11.08h1.388zm0-2.09h-1.377V8.993h1.388zm0-2.087h-1.377V6.906h1.388zm0-2.086h-1.377V4.819h1.388zm.683.683h1.393v1.389h-1.393zm0 3.475V8.993h1.389v1.388Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#A68400" d="m0 0 11.955 24L24 0zm13.366 4.827h1.393v1.38h-1.393zm-2.77 5.569H9.22V8.993h1.389zm0-2.087H9.22V6.906h1.389zm0-2.086H9.22V4.819h1.389zm2.087 6.263h-1.377V11.08h1.388zm0-2.09h-1.377V8.993h1.388zm0-2.087h-1.377V6.906h1.388zm0-2.086h-1.377V4.819h1.388zm.683.683h1.393v1.389h-1.393zm0 3.475V8.993h1.389v1.388Z"/></svg>

Before

Width:  |  Height:  |  Size: 398 B

After

Width:  |  Height:  |  Size: 398 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="285" height="285" preserveAspectRatio="xMidYMid" viewBox="-14.5 0 285 285"><path fill="#430098" d="M230.4 0c13.996 0 25.37 11.232 25.597 25.177l.003.423v233.244c0 13.996-11.235 25.37-25.177 25.597l-.423.003H25.6c-13.996 0-25.37-11.235-25.597-25.176L0 258.844V25.6C0 11.601 11.235.23 25.177.003L25.6 0zm0 14.222H25.6c-6.174 0-11.218 4.948-11.374 11.085l-.004.293v233.244c0 6.174 4.948 11.218 11.085 11.374l.293.004h204.8c6.177 0 11.218-4.947 11.374-11.084l.004-.294V25.6c0-6.272-5.102-11.378-11.378-11.378M64 184.89l32 28.444-32 28.445zM92.444 42.667v80.75c14.201-4.622 34.052-9.64 53.334-9.64 17.582 0 28.106 6.913 33.838 12.712 12.109 12.25 12.397 27.82 12.381 29.897v.058l.003 85.334h-28.444v-84.942c-.14-6.639-3.34-14.614-17.778-14.614-28.632 0-60.714 14.257-61.666 14.683l-.02.009L64 166.016V42.666zm99.556 0c-1.92 16.156-8.47 31.644-21.333 46.222h-28.445c11.179-14.667 18.198-30.101 21.334-46.222Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="285" height="285" preserveAspectRatio="xMidYMid" viewBox="-14.5 0 285 285"><path fill="#7C4DD6" d="M230.4 0c13.996 0 25.37 11.232 25.597 25.177l.003.423v233.244c0 13.996-11.235 25.37-25.177 25.597l-.423.003H25.6c-13.996 0-25.37-11.235-25.597-25.176L0 258.844V25.6C0 11.601 11.235.23 25.177.003L25.6 0zm0 14.222H25.6c-6.174 0-11.218 4.948-11.374 11.085l-.004.293v233.244c0 6.174 4.948 11.218 11.085 11.374l.293.004h204.8c6.177 0 11.218-4.947 11.374-11.084l.004-.294V25.6c0-6.272-5.102-11.378-11.378-11.378M64 184.89l32 28.444-32 28.445zM92.444 42.667v80.75c14.201-4.622 34.052-9.64 53.334-9.64 17.582 0 28.106 6.913 33.838 12.712 12.109 12.25 12.397 27.82 12.381 29.897v.058l.003 85.334h-28.444v-84.942c-.14-6.639-3.34-14.614-17.778-14.614-28.632 0-60.714 14.257-61.666 14.683l-.02.009L64 166.016V42.666zm99.556 0c-1.92 16.156-8.47 31.644-21.333 46.222h-28.445c11.179-14.667 18.198-30.101 21.334-46.222Z"/></svg>

Before

Width:  |  Height:  |  Size: 958 B

After

Width:  |  Height:  |  Size: 958 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 463 B

View File

@@ -1 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>LiveKit</title><path d="M14 10h-4v4h4v-4zM18 6h-4v4.001h4v-4zM18 14h-4v4h4v-4zM22 2h-4v4h4V2zM22 18h-4v4h4v-4z" fill="#1FD5F9"></path><path d="M6 18V2H2v20h12v-4H6z" fill="#fff"></path></svg>
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14 10h-4v4h4v-4zM18 6h-4v4.001h4v-4zM18 14h-4v4h4v-4zM22 2h-4v4h4V2zM22 18h-4v4h4v-4z" fill="#1FD5F9"></path><path d="M6 18V2H2v20h12v-4H6z" fill="#7A8291"></path></svg>

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 251 B

View File

@@ -1 +1 @@
<svg width='234' height='42' viewBox='0 0 234 42' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M18 30H6V18H18V30Z' fill='#CFCECD'/><path d='M18 12H6V30H18V12ZM24 36H0V6H24V36Z' fill='#656363'/><path d='M48 30H36V18H48V30Z' fill='#CFCECD'/><path d='M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z' fill='#656363'/><path d='M84 24V30H66V24H84Z' fill='#CFCECD'/><path d='M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z' fill='#656363'/><path d='M108 36H96V18H108V36Z' fill='#CFCECD'/><path d='M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z' fill='#656363'/><path d='M144 30H126V18H144V30Z' fill='#CFCECD'/><path d='M144 12H126V30H144V36H120V6H144V12Z' fill='#211E1E'/><path d='M168 30H156V18H168V30Z' fill='#CFCECD'/><path d='M168 12H156V30H168V12ZM174 36H150V6H174V36Z' fill='#211E1E'/><path d='M198 30H186V18H198V30Z' fill='#CFCECD'/><path d='M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z' fill='#211E1E'/><path d='M234 24V30H216V24H234Z' fill='#CFCECD'/><path d='M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z' fill='#211E1E'/></svg>
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#7A8291" d="M22 24H2V0h20zM17 4.8H7v14.4h10z"/></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 137 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Render</title><path d="M18.263.007c-3.121-.147-5.744 2.109-6.192 5.082-.018.138-.045.272-.067.405-.696 3.703-3.936 6.507-7.827 6.507a7.9 7.9 0 0 1-3.825-.979.202.202 0 0 0-.302.178V24H12v-8.999c0-1.656 1.338-3 2.987-3h2.988c3.382 0 6.103-2.817 5.97-6.244-.12-3.084-2.61-5.603-5.682-5.75"/></svg>
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#7A8291" d="M18.263.007c-3.121-.147-5.744 2.109-6.192 5.082-.018.138-.045.272-.067.405-.696 3.703-3.936 6.507-7.827 6.507-1.388 0-2.691-.356-3.825-.979a.2024.2024 0 0 0-.302.178V24H12v-8.999c0-1.656 1.338-3 2.987-3h2.988c3.382 0 6.103-2.817 5.97-6.244-.12-3.084-2.61-5.603-5.682-5.75"/></svg>

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 376 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" version="1.0" viewBox="0 0 800 600"><path d="M364.7 246.1h16.9L363.2 300h-16.9l-18.5-53.9h17.1l10.2 33h.2zM436 300h-39.4v-53.9h39.2v13.6h-23.6v7.1h20.9V279h-20.9v7.4H436zm41.6-54.8c11.3 0 19.6 5.8 22.1 15.1l-14.7 6.4c-.7-4.2-2.9-7.4-7.6-7.4-5.3 0-9.3 4.4-9.3 14s3.8 14.2 9.3 14.2c4.5 0 6.9-3.4 7.6-7.6l14.7 6.4c-2.5 9.3-10.9 15.1-22.1 15.1-16.2 0-26.1-10.7-26.1-28s9.9-28.2 26.1-28.2m50.3 54.8v-40.3h-16.2v-13.6h48.5v13.6H544V300zm97.7-26.9c0 17.2-10 28-26.1 28-16 0-26.1-10.7-26.1-28s10.2-28 26.1-28c16.1.1 26.1 10.8 26.1 28m-35.4 0c0 8.7 3.3 14.2 9.4 14.2s9.4-5.3 9.4-14.2c0-8.7-3.3-14.2-9.4-14.2s-9.4 5.3-9.4 14.2m84.4 26.9c-1.5-1.8-2-3.4-2.5-6.4l-1.1-6c-.7-3.8-2.5-4.9-5.8-4.9h-6.4V300h-16v-53.9h25.6c12.2 0 20.7 4.7 20.7 16.5 0 7.8-5.3 12.3-11.1 13.4v.2c4.7.7 7.4 3.6 8.5 8.5l2 8.5c.7 2.9 1.6 4.7 3.4 6.7.1.1-17.3.1-17.3.1m-8.2-40.7h-7.8V270h7.8c3.8 0 5.8-1.6 5.8-5.4 0-3.5-1.8-5.3-5.8-5.3m-321.5 81.2c3.3.4 5.6 2.4 5.6 6 0 4.4-2.7 7.3-8.7 7.3h-9.3v-26.1h8.9c5.1 0 8.2 2.4 8.2 6.9 0 3.1-2 5.3-4.7 5.9m-9.1-1.1h5.4c3.3 0 5.1-1.5 5.1-4.5s-1.8-4.5-5.1-4.5h-5.4zm0 11.6h5.8c3.6 0 5.4-1.6 5.4-4.5s-2-4.5-5.4-4.5h-5.8zm28-10.7 7.1-12.7h3.4l-8.9 15.8v10.2h-3.3v-10.2l-9.1-15.8h3.6zm26.7-12.7h7.6c7.3 0 12.7 4 12.7 12.9s-5.4 13.1-12.7 13.1h-7.6zm3.2 23.2h4.4c4.7 0 9.3-2.5 9.3-10.3s-4.5-10.2-9.3-10.2h-4.4zm36.2-4.3H419l-2.5 7.3h-3.3l9.4-26.1h3.6l9.4 26.1h-3.3zm-10-2.9h8.9l-4.4-12.5zm24.1 10.1v-23.2h-8.7v-2.9h20.9v2.9h-8.7v23.2zm28.3-7.2h-10.9l-2.5 7.3h-3.3l9.4-26.1h3.6l9.4 26.1h-3.3zm-9.8-2.9h8.9l-4.4-12.5zm20.7-16h7.6c7.3 0 12.7 4 12.7 12.9s-5.4 13.1-12.7 13.1h-7.6zm3.3 23.2h4.4c4.7 0 9.3-2.5 9.3-10.3s-4.5-10.2-9.3-10.2h-4.4zm44.1-10.1c0 8.4-4.5 13.4-11.3 13.4S508 349 508 340.7c0-8.4 4.5-13.4 11.3-13.4s11.3 5 11.3 13.4m-19 0c0 6.4 2.7 10.7 7.8 10.7s7.8-4.4 7.8-10.7c0-6.4-2.7-10.7-7.8-10.7s-7.8 4.3-7.8 10.7m45-.2v13.1H554l-.5-3.8c-.9 2.7-3.8 4.4-7.3 4.4-6.5 0-11.1-4.9-11.1-13.4 0-8.7 4.9-13.6 11.4-13.6 4.7 0 8 2 9.4 6l-2.9 1.6c-.9-3.1-3.4-4.9-6.7-4.9-5.1 0-8 4-8 10.7 0 6.4 2.5 10.7 8 10.7 3.4 0 6.7-1.8 6.7-6.5V343h-6.4v-2.7h9.8z"/><linearGradient id="a" x1="128.046" x2="313.226" y1="579.207" y2="472.293" gradientTransform="translate(-20.96 -225.764)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#10e7ff"/><stop offset=".103" style="stop-color:#33d5ff"/><stop offset=".255" style="stop-color:#60bdff"/><stop offset=".408" style="stop-color:#86aaff"/><stop offset=".56" style="stop-color:#a39aff"/><stop offset=".71" style="stop-color:#b790ff"/><stop offset=".858" style="stop-color:#c489ff"/><stop offset="1" style="stop-color:#c887ff"/></linearGradient><path d="m199.7 408.2-92.2-54.1V245.9l92.2-54.1 92.2 54.1v108.2zm-79.4-61.7 79.1 46.5 79.1-46.5v-92.9l-79-46.5-79.3 46.5c.1-.1.1 92.9.1 92.9" style="fill:url(#a)"/><linearGradient id="b" x1="185.354" x2="277.202" y1="542.013" y2="488.984" gradientTransform="translate(-20.96 -225.764)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#10e7ff"/><stop offset=".103" style="stop-color:#33d5ff"/><stop offset=".255" style="stop-color:#60bdff"/><stop offset=".408" style="stop-color:#86aaff"/><stop offset=".56" style="stop-color:#a39aff"/><stop offset=".71" style="stop-color:#b790ff"/><stop offset=".858" style="stop-color:#c489ff"/><stop offset="1" style="stop-color:#c887ff"/></linearGradient><path d="m239.4 270.6-32.3 56.8 10.5 18.5 42.8-75.3zm-36.1 0L171 327.4l10.5 18.5 42.8-75.3zm-64.4 0 42.8 75.3 10.5-18.5-32.3-56.8zm0 0 42.8 75.3 10.5-18.5-32.3-56.8zm64.4 0L171 327.4l10.5 18.5 42.8-75.3z" style="fill:url(#b)"/></svg>
<svg role="img" viewBox="89.7 190 220 220" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" x1="128.046" x2="313.226" y1="579.207" y2="472.293" gradientTransform="translate(-20.96 -225.764)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#10e7ff"/><stop offset=".103" style="stop-color:#33d5ff"/><stop offset=".255" style="stop-color:#60bdff"/><stop offset=".408" style="stop-color:#86aaff"/><stop offset=".56" style="stop-color:#a39aff"/><stop offset=".71" style="stop-color:#b790ff"/><stop offset=".858" style="stop-color:#c489ff"/><stop offset="1" style="stop-color:#c887ff"/></linearGradient><path d="m199.7 408.2-92.2-54.1V245.9l92.2-54.1 92.2 54.1v108.2zm-79.4-61.7 79.1 46.5 79.1-46.5v-92.9l-79-46.5-79.3 46.5c.1-.1.1 92.9.1 92.9" style="fill:url(#a)"/><linearGradient id="b" x1="185.354" x2="277.202" y1="542.013" y2="488.984" gradientTransform="translate(-20.96 -225.764)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#10e7ff"/><stop offset=".103" style="stop-color:#33d5ff"/><stop offset=".255" style="stop-color:#60bdff"/><stop offset=".408" style="stop-color:#86aaff"/><stop offset=".56" style="stop-color:#a39aff"/><stop offset=".71" style="stop-color:#b790ff"/><stop offset=".858" style="stop-color:#c489ff"/><stop offset="1" style="stop-color:#c887ff"/></linearGradient><path d="m239.4 270.6-32.3 56.8 10.5 18.5 42.8-75.3zm-36.1 0L171 327.4l10.5 18.5 42.8-75.3zm-64.4 0 42.8 75.3 10.5-18.5-32.3-56.8zm0 0 42.8 75.3 10.5-18.5-32.3-56.8zm64.4 0L171 327.4l10.5 18.5 42.8-75.3z" style="fill:url(#b)"/></svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -17,7 +17,10 @@ function ModalFixture(): JSX.Element {
return (
<>
<Button
data-testid="open-add-credit-card"
variant="solid"
color="primary"
size="md"
testId="open-add-credit-card"
onClick={(): void => setOpen(true)}
>
Contact support

View File

@@ -1,5 +1,5 @@
.breadcrumb {
padding-left: 16px;
padding: var(--spacing-4) 16px var(--spacing-2);
ol {
align-items: center;
@@ -8,6 +8,10 @@
:global(.ant-breadcrumb-separator) {
color: var(--muted-foreground);
}
[data-slot='button'] {
padding: 0px;
}
}
.divider {

View File

@@ -1,9 +1,5 @@
.item {
--button-padding: 0;
--button-font-size: var(--periscope-font-size-base);
}
.itemLast {
color: var(--muted-foreground);
font-size: var(--periscope-font-size-base);
color: var(--button-secondary-label);
font-size: var(--button-font-size, var(--periscope-font-size-base));
line-height: var(--button-size-line-height, 16px);
}

View File

@@ -1,3 +1,4 @@
import { ReactNode } from 'react';
import { Button } from '@signozhq/ui/button';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { isModifierKeyPressed } from 'utils/app';
@@ -10,7 +11,7 @@ export type BreadcrumbItemConfig =
route?: string;
}
| {
title: string | null;
title: ReactNode;
isLast?: true;
};
@@ -26,9 +27,9 @@ function BreadcrumbItem({
return (
<Button
variant="ghost"
size="md"
variant="link"
color="secondary"
className={styles.item}
onClick={(e: React.MouseEvent): void => {
if (!('route' in props) || !props.route) {
return;

View File

@@ -34,21 +34,23 @@ function ErrorEmptyState({
</div>
<div className={styles.actions}>
<Button
size="md"
variant="solid"
color="secondary"
prefix={<LifeBuoy size={14} />}
onClick={onContactSupport}
data-testid="error-contact-support-button"
testId="error-contact-support-button"
>
Contact Support
</Button>
{onRefresh && (
<Button
size="md"
variant="outlined"
color="secondary"
prefix={<RefreshCw size={14} />}
onClick={onRefresh}
data-testid="error-refresh-button"
testId="error-refresh-button"
>
Refresh
</Button>

View File

@@ -1,20 +1,32 @@
.labelColumn {
// Badges fill their wrapper; the wrapper, as the flex item, owns the cap.
--badge-max-width: 100%;
position: relative;
display: flex;
gap: 4px;
align-items: center;
overflow: hidden;
max-width: 100%;
width: 100%;
> * {
flex-shrink: 0;
max-width: 180px;
}
}
.labelBadge {
cursor: default;
font-size: 12px;
// Kept mounted so its width can still be measured.
.overflowed {
position: absolute;
visibility: hidden;
pointer-events: none;
}
--badge-display: inline;
max-width: 180px;
text-overflow: ellipsis;
// The only visible label truncates instead of pushing `+N` out of view.
.labelColumn > .shrinkable {
flex-shrink: 1;
min-width: 0;
}
.overflowTrigger {
@@ -22,11 +34,6 @@
cursor: pointer;
}
.overflowBadge {
cursor: pointer;
font-size: 12px;
}
.labelPopover {
display: flex;
flex-direction: column;

View File

@@ -1,13 +1,14 @@
import { TooltipProvider } from '@signozhq/ui/tooltip';
import { act, render, screen } from '@testing-library/react';
import LabelColumn from './LabelColumn';
let resizeCallback: ResizeObserverCallback | null = null;
const BADGE_WIDTH = 60;
let resizeCallbacks: ResizeObserverCallback[] = [];
class MockResizeObserver {
constructor(callback: ResizeObserverCallback) {
resizeCallback = callback;
resizeCallbacks.push(callback);
}
observe = jest.fn();
@@ -15,15 +16,23 @@ class MockResizeObserver {
disconnect = jest.fn();
}
function triggerResize(width: number): void {
if (resizeCallback) {
act(() => {
resizeCallback?.(
[{ contentRect: { width } } as ResizeObserverEntry],
{} as ResizeObserver,
);
// jsdom has no layout: every badge measures BADGE_WIDTH, the column `containerWidth`.
function mockLayout(containerWidth: number): void {
jest
.spyOn(HTMLElement.prototype, 'clientWidth', 'get')
.mockImplementation(function getClientWidth(this: HTMLElement) {
return this.dataset.testid === 'label-column' ? containerWidth : 0;
});
}
jest
.spyOn(HTMLElement.prototype, 'getBoundingClientRect')
.mockReturnValue({ width: BADGE_WIDTH } as DOMRect);
}
function triggerResize(containerWidth: number): void {
mockLayout(containerWidth);
act(() => {
resizeCallbacks.forEach((callback) => callback([], {} as ResizeObserver));
});
}
beforeAll(() => {
@@ -31,13 +40,14 @@ beforeAll(() => {
});
afterEach(() => {
resizeCallback = null;
resizeCallbacks = [];
jest.restoreAllMocks();
});
function renderWithProviders(
ui: React.ReactElement,
): ReturnType<typeof render> {
return render(<TooltipProvider>{ui}</TooltipProvider>);
return render(ui);
}
describe('LabelColumn', () => {
@@ -54,13 +64,10 @@ describe('LabelColumn', () => {
it('should truncate labels and show +N badge when container is narrow', () => {
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
// 3 badges (180px) + overflow reserve (40px)
mockLayout(220);
renderWithProviders(<LabelColumn labels={labels} />);
// Simulate narrow container that fits ~3 badges
// Badge widths: env=37, service=65, region=58, team=44, owner=51, version=65
// 220px available = 3 badges (160px) + gaps (8px) + overflow (44px)
triggerResize(220);
// First 3 visible
expect(screen.getByTestId('label-tag-env')).toBeInTheDocument();
expect(screen.getByTestId('label-tag-service')).toBeInTheDocument();
@@ -93,19 +100,37 @@ describe('LabelColumn', () => {
expect(screen.getByTestId('label-tag-service')).toHaveTextContent('service');
});
it('should show overflow badge with remaining count when container is narrow', () => {
it('should update the overflow count when the container resizes', () => {
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
mockLayout(220);
renderWithProviders(<LabelColumn labels={labels} />);
expect(screen.getByTestId('label-overflow-badge')).toHaveTextContent('+3');
// 2 badges (120px) + overflow reserve (40px)
triggerResize(160);
expect(screen.getByTestId('label-overflow-badge')).toHaveTextContent('+4');
triggerResize(1000);
expect(screen.queryByTestId('label-overflow-badge')).not.toBeInTheDocument();
});
it('should skip the overflow reserve when every label fits', () => {
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
mockLayout(labels.length * BADGE_WIDTH);
renderWithProviders(<LabelColumn labels={labels} />);
// Simulate narrow container to trigger overflow (shows 3 labels)
// 220px fits first 3 badges before overflow
triggerResize(220);
expect(screen.queryByTestId('label-overflow-badge')).not.toBeInTheDocument();
});
// Overflow badge shows +3 (remaining labels)
const overflowBadge = screen.getByTestId('label-overflow-badge');
expect(overflowBadge).toBeInTheDocument();
expect(overflowBadge).toHaveTextContent('+3');
it('should keep one label visible when even that one does not fit', () => {
const labels = ['env', 'service'];
mockLayout(50);
renderWithProviders(<LabelColumn labels={labels} />);
expect(screen.getByTestId('label-overflow-badge')).toHaveTextContent('+1');
});
it('should render empty when no labels provided', () => {
@@ -126,11 +151,9 @@ describe('LabelColumn', () => {
it('should show all labels when container is wide enough', () => {
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
mockLayout(1000);
renderWithProviders(<LabelColumn labels={labels} />);
// Simulate wide container
triggerResize(1000);
// All labels visible
labels.forEach((label) => {
expect(screen.getByTestId(`label-tag-${label}`)).toBeInTheDocument();

View File

@@ -1,35 +1,19 @@
import { Copy } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
import { toast } from '@signozhq/ui/sonner';
import {
TooltipContent,
TooltipRoot,
TooltipTrigger,
} from '@signozhq/ui/tooltip';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Tooltip } from '@signozhq/ui/tooltip';
import cx from 'classnames';
import { useLayoutEffect, useRef, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import LabelTag from './LabelTag';
import styles from './LabelColumn.module.scss';
import { BADGE_GAP, estimateBadgeWidth, OVERFLOW_BADGE_WIDTH } from './utils';
import { getVisibleCount } from './utils';
export interface LabelColumnProps {
labels: string[];
color?:
| 'primary'
| 'secondary'
| 'success'
| 'error'
| 'warning'
| 'robin'
| 'forest'
| 'amber'
| 'sienna'
| 'cherry'
| 'sakura'
| 'aqua'
| 'vanilla';
color?: BadgeColorType;
value?: { [key: string]: string };
}
@@ -39,60 +23,37 @@ function LabelColumn({
color = 'primary',
}: LabelColumnProps): JSX.Element {
const containerRef = useRef<HTMLDivElement>(null);
const [maxVisibleCount, setMaxVisibleCount] = useState(labels.length);
const [visibleCount, setVisibleCount] = useState(labels.length);
const [, copyToClipboard] = useCopyToClipboard();
const calculateMaxVisible = useCallback(
(width: number): number => {
if (width <= 0) {
return 1;
}
const availableWidth = width - OVERFLOW_BADGE_WIDTH - BADGE_GAP;
let usedWidth = 0;
let count = 0;
for (const label of labels) {
const badgeWidth = estimateBadgeWidth(label, value?.[label]) + BADGE_GAP;
if (usedWidth + badgeWidth > availableWidth && count > 0) {
break;
}
usedWidth += badgeWidth;
count++;
}
return Math.max(1, count);
},
[labels, value],
);
useEffect(() => {
useLayoutEffect(() => {
const container = containerRef.current;
if (!container) {
return;
}
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
if (entry && entry.contentRect.width > 0) {
setMaxVisibleCount(calculateMaxVisible(entry.contentRect.width));
const measure = (): void => {
const available = container.clientWidth;
if (available <= 0) {
return;
}
});
const widths = Array.from(container.children)
.slice(0, labels.length)
.map((item) => item.getBoundingClientRect().width);
const gap = parseFloat(getComputedStyle(container).columnGap) || 0;
setVisibleCount(getVisibleCount(widths, available, gap));
};
const observer = new ResizeObserver(measure);
observer.observe(container);
if (container.clientWidth > 0) {
setMaxVisibleCount(calculateMaxVisible(container.clientWidth));
}
measure();
return (): void => observer.disconnect();
}, [calculateMaxVisible]);
}, [labels, value]);
const needsOverflow = labels.length > maxVisibleCount;
const visibleLabels = needsOverflow
? labels.slice(0, maxVisibleCount)
: labels;
const remainingLabels = needsOverflow ? labels.slice(maxVisibleCount) : [];
const remainingLabels = labels.slice(visibleCount);
return (
<div
@@ -100,24 +61,23 @@ function LabelColumn({
className={styles.labelColumn}
data-testid="label-column"
>
{visibleLabels.map((label) => (
<LabelTag key={label} label={label} color={color} value={value?.[label]} />
{labels.map((label, index) => (
<LabelTag
key={label}
label={label}
color={color}
value={value?.[label]}
className={cx({
[styles.overflowed]: index >= visibleCount,
[styles.shrinkable]: index === 0 && visibleCount === 1,
})}
/>
))}
{remainingLabels.length > 0 && (
<TooltipRoot>
<TooltipTrigger asChild>
<span>
<Badge
color={color}
className={styles.overflowBadge}
variant="outline"
data-testid="label-overflow-badge"
>
+{remainingLabels.length}
</Badge>
</span>
</TooltipTrigger>
<TooltipContent side="bottom" align="end">
<Tooltip
side="bottom"
align="end"
title={
<div className={styles.tooltipContent}>
<span>
{remainingLabels
@@ -140,8 +100,14 @@ function LabelColumn({
<Copy size={12} />
</button>
</div>
</TooltipContent>
</TooltipRoot>
}
>
<span>
<Badge color={color} variant="outlined" testId="label-overflow-badge">
+{remainingLabels.length}
</Badge>
</span>
</Tooltip>
)}
</div>
);

View File

@@ -1,11 +1,3 @@
.labelBadge {
cursor: default;
font-size: 12px;
max-width: 180px;
text-overflow: ellipsis;
}
.labelValue {
text-overflow: ellipsis;
overflow: hidden;

View File

@@ -1,35 +1,24 @@
import { Copy } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
import { toast } from '@signozhq/ui/sonner';
import {
TooltipContent,
TooltipRoot,
TooltipTrigger,
} from '@signozhq/ui/tooltip';
import { Tooltip } from '@signozhq/ui/tooltip';
import { useCopyToClipboard } from 'react-use';
import styles from './LabelTag.module.scss';
export interface LabelTagProps {
label: string;
color?:
| 'primary'
| 'secondary'
| 'success'
| 'error'
| 'warning'
| 'robin'
| 'forest'
| 'amber'
| 'sienna'
| 'cherry'
| 'sakura'
| 'aqua'
| 'vanilla';
color?: BadgeColorType;
value?: string;
className?: string;
}
function LabelTag({ label, value, color }: LabelTagProps): JSX.Element {
function LabelTag({
label,
value,
color,
className,
}: LabelTagProps): JSX.Element {
const [, copyToClipboard] = useCopyToClipboard();
const displayText = value ? `${label}: ${value}` : label;
const searchFormat = value ? `${label} ${value}` : label;
@@ -41,20 +30,8 @@ function LabelTag({ label, value, color }: LabelTagProps): JSX.Element {
};
return (
<TooltipRoot>
<TooltipTrigger asChild>
<span>
<Badge
color={color}
className={styles.labelBadge}
variant="outline"
data-testid={`label-tag-${label}`}
>
<span className={styles.labelValue}>{displayText}</span>
</Badge>
</span>
</TooltipTrigger>
<TooltipContent>
<Tooltip
title={
<div className={styles.tooltipContent}>
<span>{displayText}</span>
<button
@@ -66,8 +43,19 @@ function LabelTag({ label, value, color }: LabelTagProps): JSX.Element {
<Copy size={12} />
</button>
</div>
</TooltipContent>
</TooltipRoot>
}
>
<span className={className}>
<Badge
textTransform="none"
color={color ?? 'secondary'}
variant="outlined"
testId={`label-tag-${label}`}
>
<span className={styles.labelValue}>{displayText}</span>
</Badge>
</span>
</Tooltip>
);
}

View File

@@ -1,14 +1,33 @@
export const BADGE_GAP = 4;
export const OVERFLOW_BADGE_WIDTH = 40;
export const BADGE_MAX_WIDTH = 180;
export const BADGE_PADDING = 16;
export const CHAR_WIDTH = 7;
export function estimateBadgeWidth(label: string, value?: string): number {
const displayText = value ? `${label}: ${value}` : label;
return Math.min(
displayText.length * CHAR_WIDTH + BADGE_PADDING,
BADGE_MAX_WIDTH,
/**
* How many leading labels fit in `available` px, reserving room for the `+N` badge
* whenever some are left out. At least one label is always shown.
*/
export function getVisibleCount(
widths: number[],
available: number,
gap: number,
): number {
const total = widths.reduce(
(sum, width, index) => sum + width + (index > 0 ? gap : 0),
0,
);
if (total <= available) {
return widths.length;
}
const limit = available - OVERFLOW_BADGE_WIDTH - gap;
let used = 0;
let count = 0;
for (const width of widths) {
used += width + (count > 0 ? gap : 0);
if (used > limit) {
break;
}
count++;
}
return Math.max(1, count);
}

View File

@@ -30,21 +30,23 @@ function NoResultsEmptyState({
<div className={styles.actions}>
{onClear && (
<Button
size="md"
variant="outlined"
color="secondary"
onClick={onClear}
data-testid="no-results-clear-button"
testId="no-results-clear-button"
>
{clearButtonText}
</Button>
)}
{onRefresh && (
<Button
size="md"
variant="outlined"
color="secondary"
prefix={<RefreshCw size={14} />}
onClick={onRefresh}
data-testid="no-results-refresh-button"
testId="no-results-refresh-button"
>
Refresh
</Button>

View File

@@ -1,4 +1,4 @@
import type { BadgeColor } from '@signozhq/ui/badge';
import type { BadgeColorType } from '@signozhq/ui/badge';
export const STATE_ORDER = ['firing', 'pending', 'inactive', 'disabled'];
export const SEVERITY_ORDER = ['critical', 'error', 'warning', 'info'];
@@ -24,9 +24,9 @@ export const SEVERITY_COLORS: Record<string, string> = {
info: 'var(--bg-robin-500)',
};
export const SEVERITY_BADGE_COLORS: Record<string, BadgeColor> = {
critical: 'error',
error: 'error',
export const SEVERITY_BADGE_COLORS: Record<string, BadgeColorType> = {
critical: 'danger',
error: 'danger',
warning: 'warning',
info: 'primary',
};

View File

@@ -33,36 +33,3 @@
color: var(--l1-foreground);
white-space: nowrap;
}
.auth-header-help-button {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
height: 32px;
padding: 10px 16px;
background: var(--l2-background);
color: var(--l2-foreground);
border: none;
border-radius: 2px;
cursor: pointer;
transition: opacity 0.2s ease;
span {
font-family: var(--font-family-inter, Inter, sans-serif);
font-size: 11px;
font-weight: 500;
line-height: 1;
color: var(--l2-foreground);
text-align: center;
}
svg {
flex-shrink: 0;
color: var(--l2-foreground);
}
&:hover {
opacity: 0.8;
}
}

View File

@@ -22,11 +22,11 @@ function AuthHeader(): JSX.Element {
<span className="auth-header-logo-text">SigNoz</span>
</div>
<Button
className="auth-header-help-button"
size="md"
prefix={<LifeBuoy size={12} />}
onClick={handleGetHelp}
variant="solid"
color="none"
color="secondary"
>
Get Help
</Button>

View File

@@ -1,7 +1,7 @@
import React, { Dispatch, SetStateAction, useState } from 'react';
import { Check, Plus, X } from '@signozhq/icons';
import { Button, Flex } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Pill } from '@signozhq/ui/pill';
import Input from 'components/Input';
import './Badges.styles.scss';
@@ -47,18 +47,13 @@ function Badges({ tags, setTags }: AddTagsProps): JSX.Element {
return (
<div className="tags-container">
{tags.map<React.ReactNode>((tag) => (
<Badge
<Pill.Closeable
textTransform="none"
key={tag}
color="vanilla"
style={{ userSelect: 'none' }}
closable
onClose={(e): void => {
e.preventDefault();
handleClose(tag);
}}
onClose={() => handleClose(tag)}
>
{tag}
</Badge>
</Pill.Closeable>
))}
{inputVisible && (

View File

@@ -39,9 +39,3 @@
}
}
}
.cloud-service-data-collected-table-tooltip {
max-width: 280px;
white-space: normal;
word-break: break-word;
}

View File

@@ -4,7 +4,7 @@ import {
CloudintegrationtypesCollectedMetricDTO,
} from 'api/generated/services/sigNoz.schemas';
import { BarChart, Info, ScrollText } from '@signozhq/icons';
import { TooltipProvider, TooltipSimple } from '@signozhq/ui/tooltip';
import { Tooltip } from '@signozhq/ui/tooltip';
import './CloudServiceDataCollected.styles.scss';
@@ -88,23 +88,15 @@ function CloudServiceDataCollected({
<BarChart size={14} />
Metrics
{metricsInfoTooltip && (
<TooltipProvider>
<TooltipSimple
title={metricsInfoTooltip}
side="top"
tooltipContentProps={{
className: 'cloud-service-data-collected-table-tooltip',
}}
<Tooltip title={metricsInfoTooltip} side="top">
<span
className="cloud-service-data-collected-table-heading-info"
aria-label="About the metrics listed below"
data-testid="data-collected-metrics-info"
>
<span
className="cloud-service-data-collected-table-heading-info"
aria-label="About the metrics listed below"
data-testid="data-collected-metrics-info"
>
<Info size={12} />
</span>
</TooltipSimple>
</TooltipProvider>
<Info size={12} />
</span>
</Tooltip>
)}
</div>
<Table

View File

@@ -2,6 +2,13 @@
position: relative;
}
.copyButton {
position: absolute;
right: 8px;
top: 8px;
z-index: 1;
}
.codeBlockSyntaxHighlighter {
background-color: var(--l2-background) !important;
border-radius: 4px !important;

View File

@@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Check, Copy } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Tooltip } from '@signozhq/ui/tooltip';
import SyntaxHighlighter, {
a11yDark,
} from 'components/MarkdownRenderer/syntaxHighlighter';
@@ -52,16 +53,20 @@ function CodeBlock({
data-testid="code-block-container"
>
{showCopyButton ? (
<Button
variant="ghost"
color="secondary"
size="sm"
onClick={handleCopy}
prefix={isCopied ? <Check size={14} /> : <Copy size={14} />}
aria-label="Copy code"
title={isCopied ? 'Copied' : 'Copy'}
style={{ position: 'absolute', right: 8, top: 8, zIndex: 1 }}
/>
<div className={styles.copyButton}>
<Tooltip title={isCopied ? 'Copied' : 'Copy'}>
<Button
variant="ghost"
color="secondary"
size="sm"
icon
onClick={handleCopy}
aria-label="Copy code"
>
{isCopied ? <Check size={14} /> : <Copy size={14} />}
</Button>
</Tooltip>
</div>
) : null}
<SyntaxHighlighter
style={a11yDark}

View File

@@ -134,26 +134,27 @@ function CreateServiceAccountModal(): JSX.Element {
<DialogFooter className="create-sa-modal__footer">
<Button
size="md"
type="button"
variant="solid"
color="secondary"
onClick={handleClose}
data-testid="create-sa-cancel-btn"
testId="create-sa-cancel-btn"
prefix={<X size={12} />}
>
<X size={12} />
Cancel
</Button>
<AuthZButton
size="md"
checks={[SACreatePermission]}
withPortal={false}
type="submit"
form="create-sa-form"
variant="solid"
color="primary"
loading={isSubmitting}
disabled={!isValid}
data-testid="create-sa-submit-btn"
testId="create-sa-submit-btn"
>
Create Service Account
</AuthZButton>

View File

@@ -5,6 +5,7 @@ import {
} from 'lib/authz/utils/authz-test-utils';
import { rest, server } from 'mocks-server/server';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import { submitImplicitly } from 'tests/submitImplicitly';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import CreateServiceAccountModal from '../CreateServiceAccountModal';
@@ -60,7 +61,10 @@ describe('CreateServiceAccountModal', () => {
await screen.findByTestId('create-sa-name-input');
await waitFor(() =>
expect(screen.getByTestId('create-sa-submit-btn')).toBeDisabled(),
expect(screen.getByTestId('create-sa-submit-btn')).toHaveAttribute(
'aria-disabled',
'true',
),
);
});
@@ -72,10 +76,14 @@ describe('CreateServiceAccountModal', () => {
const submitBtn = await screen.findByTestId('create-sa-submit-btn');
await user.type(nameInput, 'test');
await waitFor(() => expect(submitBtn).not.toBeDisabled());
await waitFor(() =>
expect(submitBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
await user.clear(nameInput);
await waitFor(() => expect(submitBtn).toBeDisabled());
await waitFor(() =>
expect(submitBtn).toHaveAttribute('aria-disabled', 'true'),
);
});
it('successful submit shows toast.success and closes modal', async () => {
@@ -86,7 +94,9 @@ describe('CreateServiceAccountModal', () => {
await user.type(nameInput, 'Deploy Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
await waitFor(() => expect(submitBtn).not.toBeDisabled());
await waitFor(() =>
expect(submitBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
await user.click(submitBtn);
await waitFor(() => {
@@ -102,6 +112,40 @@ describe('CreateServiceAccountModal', () => {
});
});
it('pressing Enter again while creating sends one request', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
let requests = 0;
server.use(
rest.post(SERVICE_ACCOUNTS_ENDPOINT, (_, res, ctx) => {
requests += 1;
return res(
ctx.delay(100),
ctx.status(201),
ctx.json({ status: 'success', data: {} }),
);
}),
);
renderModal();
const nameInput = await screen.findByTestId<HTMLInputElement>(
'create-sa-name-input',
);
await user.type(nameInput, 'Deploy Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
await waitFor(() =>
expect(submitBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
submitImplicitly(nameInput);
await waitFor(() => expect(submitBtn).toHaveAttribute('aria-busy', 'true'));
submitImplicitly(nameInput);
await waitFor(() => {
expect(mockToast.success).toHaveBeenCalled();
});
expect(requests).toBe(1);
});
it('shows toast.error on API error and keeps modal open', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
@@ -120,7 +164,9 @@ describe('CreateServiceAccountModal', () => {
await user.type(nameInput, 'Dupe Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
await waitFor(() => expect(submitBtn).not.toBeDisabled());
await waitFor(() =>
expect(submitBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
await user.click(submitBtn);
await waitFor(() => {
@@ -180,7 +226,10 @@ describe('CreateServiceAccountModal', () => {
).resolves.toBeInTheDocument();
// The footer lives outside the guard: submit is gated, Cancel still works.
expect(screen.getByTestId('create-sa-submit-btn')).toBeDisabled();
expect(screen.getByTestId('create-sa-submit-btn')).toHaveAttribute(
'aria-disabled',
'true',
);
await user.click(screen.getByTestId('create-sa-cancel-btn'));

View File

@@ -4,30 +4,6 @@
align-items: center;
gap: 4px;
.zoom-out-btn {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--secondary-foreground);
background-color: var(--secondary-background);
border: 1px solid var(--secondary-border);
border-radius: 2px;
box-shadow: none;
padding: 10px;
height: 33px;
&:hover:not(:disabled) {
color: var(--primary-foreground);
background: var(--primary-background);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
.timeSelection-input {
&:hover {
border-color: var(--l1-border) !important;

View File

@@ -72,6 +72,8 @@ interface CustomTimePickerProps {
maxTime: number;
/** When true, zoom-out button is hidden (e.g. in drawer/modal time selection) */
isModalTimeSelection?: boolean;
/** Lands on the trigger input. Not spread — the component takes no rest props. */
'data-testid'?: string;
}
function CustomTimePicker({
@@ -95,6 +97,7 @@ function CustomTimePicker({
minTime,
maxTime,
isModalTimeSelection = false,
'data-testid': dataTestId,
}: CustomTimePickerProps): JSX.Element {
const [selectedTimePlaceholderValue, setSelectedTimePlaceholderValue] =
useState('Select / Enter Time Range');
@@ -596,6 +599,7 @@ function CustomTimePicker({
>
<Input
ref={inputRef}
data-testid={dataTestId}
autoComplete="off"
className={cx(
'timeSelection-input',
@@ -650,20 +654,20 @@ function CustomTimePicker({
</Popover>
</Tooltip>
{!showLiveLogs && !isModalTimeSelection && (
<Tooltip
title={
zoomOutDisabled ? 'Zoom out time range is limited to 1 month' : 'Zoom out'
}
>
<Tooltip title={zoomOutDisabled ? undefined : 'Zoom out'}>
<Button
className="zoom-out-btn"
disabledTooltip="Zoom out time range is limited to 1 month"
size="md"
onClick={handleZoomOut}
disabled={zoomOutDisabled}
data-testid="zoom-out-btn"
prefix={<ZoomOut size={14} />}
testId="zoom-out-btn"
icon
aria-label="Zoom out"
variant="solid"
color="none"
/>
color="secondary"
>
<ZoomOut size={14} />
</Button>
</Tooltip>
)}
</div>
@@ -682,4 +686,5 @@ CustomTimePicker.defaultProps = {
onExitLiveLogs: noop,
showLiveLogs: false,
showRecentlyUsed: true,
'data-testid': undefined,
};

View File

@@ -180,6 +180,7 @@ function CustomTimePickerPopoverContent({
type="text"
className="time-btns"
key={option.label + option.value}
data-testid={`time-chip-${option.value}`}
onClick={(): void => {
handleExitLiveLogs();
onSelectHandler(option.label, option.value);
@@ -259,6 +260,7 @@ function CustomTimePickerPopoverContent({
<Button
type="text"
key={option.label + option.value}
data-testid={`time-option-${option.value}`}
onClick={(e: React.MouseEvent<HTMLButtonElement>): void => {
e.stopPropagation();
e.preventDefault();

View File

@@ -164,6 +164,6 @@ describe('CustomTimePicker - zoom out button', () => {
);
const zoomOutBtn = screen.getByTestId('zoom-out-btn');
expect(zoomOutBtn).toBeDisabled();
expect(zoomOutBtn).toHaveAttribute('aria-disabled', 'true');
});
});

View File

@@ -9,3 +9,8 @@
margin-inline-end: 12px;
flex-shrink: 0;
}
.name {
color: var(--danger-background);
font-weight: var(--font-weight-medium);
}

View File

@@ -27,12 +27,14 @@ function DetailsHeader({
const closeButton = (
<Button
variant="ghost"
size="icon"
size="sm"
icon
color="secondary"
onClick={onClose}
aria-label="Close"
prefix={<X size={14} />}
></Button>
>
<X size={14} />
</Button>
);
return (

View File

@@ -42,12 +42,5 @@
height: 1px;
background: var(--l1-border);
}
.export-button {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
}

View File

@@ -308,13 +308,16 @@ describe.each([
fireEvent.click(screen.getByText('Export'));
expect(screen.getByTestId(testId)).toBeDisabled();
expect(screen.getByTestId(testId)).toHaveAttribute('aria-disabled', 'true');
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
resolveDownload!();
await waitFor(() => {
expect(screen.getByTestId(testId)).not.toBeDisabled();
expect(screen.getByTestId(testId)).not.toHaveAttribute(
'aria-disabled',
'true',
);
});
});
});

View File

@@ -1,7 +1,7 @@
import { useCallback, useMemo, useState } from 'react';
import { Popover, Tooltip } from 'antd';
import { Button } from '@signozhq/ui/button';
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
import { RadioGroup } from '@signozhq/ui/radio-group';
import { Typography } from '@signozhq/ui/typography';
import { TelemetryFieldKey } from 'api/v5/v5';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -69,10 +69,15 @@ export default function DownloadOptionsMenu({
>
<div className="export-format">
<Typography.Text className="title">FORMAT</Typography.Text>
<RadioGroup value={exportFormat} onChange={setExportFormat}>
<RadioGroupItem value={DownloadFormats.CSV}>csv</RadioGroupItem>
<RadioGroupItem value={DownloadFormats.JSONL}>jsonl</RadioGroupItem>
</RadioGroup>
<RadioGroup
color="primary"
value={exportFormat}
onChange={setExportFormat}
items={[
{ value: DownloadFormats.CSV, label: 'csv' },
{ value: DownloadFormats.JSONL, label: 'jsonl' },
]}
/>
</div>
<div className="horizontal-line" />
@@ -80,19 +85,15 @@ export default function DownloadOptionsMenu({
<div className="row-limit">
<Typography.Text className="title">Number of Rows</Typography.Text>
<RadioGroup
color="primary"
value={String(rowLimit)}
onChange={(value): void => setRowLimit(Number(value))}
>
<RadioGroupItem value={String(DownloadRowCounts.TEN_K)}>
10k
</RadioGroupItem>
<RadioGroupItem value={String(DownloadRowCounts.THIRTY_K)}>
30k
</RadioGroupItem>
<RadioGroupItem value={String(DownloadRowCounts.FIFTY_K)}>
50k
</RadioGroupItem>
</RadioGroup>
items={[
{ value: String(DownloadRowCounts.TEN_K), label: '10k' },
{ value: String(DownloadRowCounts.THIRTY_K), label: '30k' },
{ value: String(DownloadRowCounts.FIFTY_K), label: '50k' },
]}
/>
</div>
{dataSource !== DataSource.TRACES && (
@@ -101,12 +102,15 @@ export default function DownloadOptionsMenu({
<div className="columns-scope">
<Typography.Text className="title">Columns</Typography.Text>
<RadioGroup value={columnsScope} onChange={setColumnsScope}>
<RadioGroupItem value={DownloadColumnsScopes.ALL}>All</RadioGroupItem>
<RadioGroupItem value={DownloadColumnsScopes.SELECTED}>
Selected
</RadioGroupItem>
</RadioGroup>
<RadioGroup
color="primary"
value={columnsScope}
onChange={setColumnsScope}
items={[
{ value: DownloadColumnsScopes.ALL, label: 'All' },
{ value: DownloadColumnsScopes.SELECTED, label: 'Selected' },
]}
/>
</div>
</>
)}
@@ -115,9 +119,9 @@ export default function DownloadOptionsMenu({
variant="solid"
color="primary"
prefix={<Download size={16} />}
size="md"
width="100%"
onClick={handleExport}
className="export-button"
disabled={isDownloading}
loading={isDownloading}
>
Export
@@ -148,13 +152,14 @@ export default function DownloadOptionsMenu({
<Button
variant="ghost"
color="secondary"
size="icon"
prefix={<Download size={14} />}
size="sm"
icon
aria-label="Download"
data-testid={`periscope-btn-download-${dataSource}`}
disabled={isDownloading}
testId={`periscope-btn-download-${dataSource}`}
loading={isDownloading}
/>
>
<Download size={14} />
</Button>
</Tooltip>
</Popover>
);

View File

@@ -38,18 +38,23 @@ function DeleteMemberDialog({
const footer = (
<>
<Button variant="solid" color="secondary" onClick={onClose}>
<X size={12} />
<Button
size="md"
variant="solid"
color="secondary"
onClick={onClose}
prefix={<X size={12} />}
>
Cancel
</Button>
<Button
size="md"
variant="solid"
color="destructive"
disabled={isDeleting}
color="danger"
onClick={onConfirm}
loading={isDeleting}
prefix={<Trash2 size={12} />}
>
<Trash2 size={12} />
{isDeleting ? 'Processing...' : title}
</Button>
</>

View File

@@ -90,7 +90,6 @@
font-weight: var(--uppercase-small-500-font-weight);
line-height: 100%;
letter-spacing: 0.44px;
text-transform: uppercase;
}
}
@@ -113,6 +112,7 @@
&__footer-left {
display: flex;
align-items: center;
gap: var(--spacing-4);
}
&__footer-right {
@@ -128,10 +128,6 @@
flex-shrink: 0;
}
&__tooltip-wrapper {
display: inline-flex;
}
&__footer-btn {
display: inline-flex;
align-items: center;
@@ -200,6 +196,7 @@
background: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: 2px;
padding-right: var(--spacing-4);
}
&__link-text-wrap {
@@ -220,8 +217,4 @@
line-height: var(--line-height-18);
letter-spacing: -0.07px;
}
&__copy-btn {
border-left: 1px solid var(--l1-border);
}
}

View File

@@ -519,7 +519,12 @@ function EditMemberDrawer({
localRoles.map((roleId) => {
const role = availableRoles.find((r) => r.id === roleId);
return (
<Badge key={roleId} color="vanilla">
<Badge
textTransform="none"
variant="solid"
key={roleId}
color="secondary"
>
{role?.name ?? roleId}
</Badge>
);
@@ -559,15 +564,15 @@ function EditMemberDrawer({
<div className="edit-member-drawer__meta-item">
<span className="edit-member-drawer__meta-label">Status</span>
{member?.status === MemberStatus.Active ? (
<Badge color="forest" variant="outline">
<Badge color="success" variant="outlined">
ACTIVE
</Badge>
) : member?.status === MemberStatus.Deleted ? (
<Badge color="cherry" variant="outline">
<Badge color="danger" variant="outlined">
DELETED
</Badge>
) : (
<Badge color="amber" variant="outline">
<Badge color="warning" variant="outlined">
INVITED
</Badge>
)}
@@ -575,12 +580,16 @@ function EditMemberDrawer({
<div className="edit-member-drawer__meta-item">
<span className="edit-member-drawer__meta-label">{joinedOnLabel}</span>
<Badge color="vanilla">{formatTimestamp(member?.joinedOn)}</Badge>
<Badge textTransform="none" variant="solid" color="secondary">
{formatTimestamp(member?.joinedOn)}
</Badge>
</div>
{!isInvited && (
<div className="edit-member-drawer__meta-item">
<span className="edit-member-drawer__meta-label">Last Modified</span>
<Badge color="vanilla">{formatTimestamp(member?.updatedAt)}</Badge>
<Badge textTransform="none" variant="solid" color="secondary">
{formatTimestamp(member?.updatedAt)}
</Badge>
</div>
)}
</div>
@@ -611,55 +620,59 @@ function EditMemberDrawer({
{!isDeleted && (
<>
<div className="edit-member-drawer__footer-left">
<Tooltip title={getDeleteTooltip(isRootUser, isSelf)}>
<span className="edit-member-drawer__tooltip-wrapper">
<Button
onClick={(): void => setShowDeleteConfirm(true)}
disabled={isRootUser || isSelf}
variant="link"
color="destructive"
>
<Trash2 size={12} />
{isInvited ? 'Revoke Invite' : 'Delete Member'}
</Button>
</span>
</Tooltip>
<Button
disabledTooltip={getDeleteTooltip(isRootUser, isSelf)}
size="md"
onClick={(): void => setShowDeleteConfirm(true)}
disabled={isRootUser || isSelf}
variant="link"
color="danger"
prefix={<Trash2 size={12} />}
>
{isInvited ? 'Revoke Invite' : 'Delete Member'}
</Button>
<div className="edit-member-drawer__footer-divider" />
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
<span className="edit-member-drawer__tooltip-wrapper">
<Button
onClick={handleGenerateResetLink}
disabled={isGeneratingLink || isRootUser || isLoadingTokenStatus}
variant="link"
color="warning"
>
<RefreshCw size={12} />
{isGeneratingLink
? 'Generating...'
: isInvited
? getInviteButtonLabel(
isLoadingTokenStatus,
existingToken,
isTokenExpired,
tokenNotFound,
)
: 'Generate Password Reset Link'}
</Button>
</span>
</Tooltip>
<Button
disabledTooltip={ROOT_USER_TOOLTIP}
size="md"
onClick={handleGenerateResetLink}
disabled={isRootUser}
loading={isGeneratingLink || isLoadingTokenStatus}
variant="link"
color="warning"
prefix={<RefreshCw size={12} />}
>
{isGeneratingLink
? 'Generating...'
: isInvited
? getInviteButtonLabel(
isLoadingTokenStatus,
existingToken,
isTokenExpired,
tokenNotFound,
)
: 'Generate Password Reset Link'}
</Button>
</div>
<div className="edit-member-drawer__footer-right">
<Button variant="outlined" color="secondary" onClick={handleClose}>
<X size={14} />
<Button
size="md"
variant="outlined"
color="secondary"
onClick={handleClose}
prefix={<X size={14} />}
>
Cancel
</Button>
<Button
disabledTooltip={isRootUser ? ROOT_USER_TOOLTIP : 'No changes to save'}
size="md"
variant="solid"
color="primary"
disabled={!isDirty || isSaving || isRootUser}
disabled={!isDirty || isRootUser}
onClick={handleSave}
loading={isSaving}
>

View File

@@ -45,11 +45,11 @@ function ResetLinkDialog({
<span className="reset-link-dialog__link-text">{resetLink}</span>
</div>
<Button
size="md"
variant="link"
color="secondary"
onClick={onCopy}
prefix={hasCopied ? <Check size={12} /> : <Copy size={12} />}
className="reset-link-dialog__copy-btn"
>
{hasCopied ? 'Copied!' : 'Copy'}
</Button>

View File

@@ -251,7 +251,7 @@ describe('EditMemberDrawer', () => {
expect(screen.getByText('ACTIVE')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /save member details/i }),
).toBeDisabled();
).toHaveAttribute('aria-disabled', 'true');
});
it('enables Save after editing name and calls updateUser on confirm', async () => {
@@ -271,7 +271,9 @@ describe('EditMemberDrawer', () => {
await user.type(nameInput, 'Alice Updated');
const saveBtn = screen.getByRole('button', { name: /save member details/i });
await waitFor(() => expect(saveBtn).not.toBeDisabled());
await waitFor(() =>
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
await user.click(saveBtn);
@@ -295,7 +297,9 @@ describe('EditMemberDrawer', () => {
await user.type(nameInput, 'Alice Updated');
const saveBtn = screen.getByRole('button', { name: /save member details/i });
await waitFor(() => expect(saveBtn).not.toBeDisabled());
await waitFor(() =>
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
await user.click(saveBtn);
await waitFor(() => {
@@ -323,7 +327,9 @@ describe('EditMemberDrawer', () => {
await user.click(await screen.findByTitle('signoz-editor'));
const saveBtn = screen.getByRole('button', { name: /save member details/i });
await waitFor(() => expect(saveBtn).not.toBeDisabled());
await waitFor(() =>
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
await user.click(saveBtn);
await waitFor(() => {
@@ -349,7 +355,9 @@ describe('EditMemberDrawer', () => {
await user.click(removeBtn);
const saveBtn = screen.getByRole('button', { name: /save member details/i });
await waitFor(() => expect(saveBtn).not.toBeDisabled());
await waitFor(() =>
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
await user.click(saveBtn);
await waitFor(() => {
@@ -505,7 +513,9 @@ describe('EditMemberDrawer', () => {
await user.type(nameInput, 'Bob Updated');
const saveBtn = screen.getByRole('button', { name: /save member details/i });
await waitFor(() => expect(saveBtn).not.toBeDisabled());
await waitFor(() =>
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
await user.click(saveBtn);
await waitFor(() => {
@@ -541,7 +551,9 @@ describe('EditMemberDrawer', () => {
await user.type(nameInput, 'Alice Updated');
const saveBtn = screen.getByRole('button', { name: /save member details/i });
await waitFor(() => expect(saveBtn).not.toBeDisabled());
await waitFor(() =>
expect(saveBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
await user.click(saveBtn);
await waitFor(() => {
@@ -619,7 +631,7 @@ describe('EditMemberDrawer', () => {
renderDrawer({ member: selfMember });
expect(
screen.getByRole('button', { name: /delete member/i }),
).toBeDisabled();
).toHaveAttribute('aria-disabled', 'true');
});
it('does not open delete confirm dialog when Delete is clicked while disabled (isSelf)', async () => {
@@ -642,7 +654,7 @@ describe('EditMemberDrawer', () => {
renderDrawer({ member: selfMember });
expect(
screen.getByRole('button', { name: /generate password reset link/i }),
).not.toBeDisabled();
).not.toHaveAttribute('aria-disabled', 'true');
});
});
@@ -664,21 +676,21 @@ describe('EditMemberDrawer', () => {
renderDrawer();
expect(
screen.getByRole('button', { name: /delete member/i }),
).toBeDisabled();
).toHaveAttribute('aria-disabled', 'true');
});
it('disables Reset Link button for root user', () => {
renderDrawer();
expect(
screen.getByRole('button', { name: /generate password reset link/i }),
).toBeDisabled();
).toHaveAttribute('aria-disabled', 'true');
});
it('disables Save button for root user', () => {
renderDrawer();
expect(
screen.getByRole('button', { name: /save member details/i }),
).toBeDisabled();
).toHaveAttribute('aria-disabled', 'true');
});
it('does not open delete confirm dialog when Delete is clicked while disabled (root)', async () => {

View File

@@ -53,7 +53,7 @@ function ErrorModal({
onClick={(): void => setVisible(true)}
onKeyDown={undefined}
>
<Badge color="error">
<Badge variant="solid" color="danger">
<CircleAlert size={14} color={Color.BG_CHERRY_500} /> error
</Badge>
</span>

View File

@@ -1,7 +1,7 @@
import { useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Button, Col, Popover, Row, Select, Space } from 'antd';
import { DropdownMenuSimple, type MenuProps } from '@signozhq/ui/dropdown-menu';
import { Dropdown, type DropdownItemType } from '@signozhq/ui/dropdown';
import { Typography } from '@signozhq/ui/typography';
import axios from 'axios';
import TextToolTip from 'components/TextToolTip';
@@ -137,16 +137,15 @@ function ExplorerCard({
);
};
const moreOptionMenu: MenuProps = {
items: [
{
key: 'delete',
label: <Typography.Text strong>Delete</Typography.Text>,
onClick: onDeleteHandler,
icon: <Trash2 size="md" />,
},
],
};
const moreOptionItems: DropdownItemType[] = [
{
type: 'item',
value: 'delete',
label: <Typography.Text strong>Delete</Typography.Text>,
onClick: onDeleteHandler,
prefix: <Trash2 size="md" />,
},
];
const saveButtonType = isQueryUpdated ? 'default' : 'primary';
const saveButtonIcon = isQueryUpdated ? null : <Save size="md" />;
@@ -230,9 +229,14 @@ function ExplorerCard({
</Popover>
<Share2 onClick={onCopyUrlHandler} size="md" />
{viewKey && (
<DropdownMenuSimple menu={moreOptionMenu}>
<Dropdown
items={moreOptionItems}
nativeButton
align="end"
side="bottom"
>
<Button type="text" size="small" icon={<Ellipsis size="md" />} />
</DropdownMenuSimple>
</Dropdown>
)}
</Space>
</OffSetCol>

View File

@@ -26,8 +26,4 @@
font-size: var(--periscope-font-size-base);
}
}
.export-button {
width: 100%;
}
}

View File

@@ -1,8 +1,8 @@
import { Download } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import { RadioGroup } from '@signozhq/ui/radio-group';
import { Tooltip } from '@signozhq/ui/tooltip';
import { Typography } from '@signozhq/ui/typography';
import {
ClientExportData,
@@ -51,36 +51,41 @@ export default function ExportMenu({
return (
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
<TooltipSimple title="Download">
<Tooltip title="Download">
<PopoverTrigger asChild>
<Button
variant="ghost"
color="secondary"
size="icon"
size="sm"
icon
aria-label="Download"
data-testid={`export-menu-${dataSource}`}
disabled={isExporting}
testId={`export-menu-${dataSource}`}
loading={isExporting}
>
<Download size={14} />
</Button>
</PopoverTrigger>
</TooltipSimple>
</Tooltip>
<PopoverContent align="end" className="export-menu-popover">
<div className="export-format">
<Typography.Text className="title">FORMAT</Typography.Text>
<RadioGroup value={exportFormat} onChange={setExportFormat}>
<RadioGroupItem value={ExportFormat.Csv}>csv</RadioGroupItem>
<RadioGroupItem value={ExportFormat.Jsonl}>jsonl</RadioGroupItem>
</RadioGroup>
<RadioGroup
color="primary"
value={exportFormat}
onChange={setExportFormat}
items={[
{ value: ExportFormat.Csv, label: 'csv' },
{ value: ExportFormat.Jsonl, label: 'jsonl' },
]}
/>
</div>
<Button
size="md"
variant="solid"
color="primary"
className="export-button"
width="100%"
onClick={handleExport}
disabled={isExporting}
loading={isExporting}
prefix={<Download size={16} />}
>

View File

@@ -80,6 +80,6 @@ describe('ExportMenu', () => {
mockIsExporting = true;
renderMenu();
expect(screen.getByTestId(TEST_ID)).toBeDisabled();
expect(screen.getByTestId(TEST_ID)).toHaveAttribute('aria-disabled', 'true');
});
});

View File

@@ -57,9 +57,8 @@ function SortableField({
</div>
{!isRequired && (
<Button
className={cx(styles.removeBtn, 'periscope-btn')}
variant="outlined"
color="destructive"
variant="solid"
color="danger"
size="sm"
onClick={(): void => onRemove(field)}
>

View File

@@ -98,11 +98,15 @@
user-select: none;
font-size: 13px;
> [data-slot='button'] {
opacity: 0;
transition: opacity 0.15s ease-in-out;
}
&:hover {
background-color: var(--l2-background);
.removeBtn,
.addBtn {
> [data-slot='button'] {
opacity: 1;
}
}
@@ -137,14 +141,6 @@
height: 32px;
}
.removeBtn,
.addBtn {
padding: 4px 10px;
opacity: 0;
transition: opacity 0.15s ease-in-out;
flex-shrink: 0;
}
.footer {
display: flex;
gap: 12px;

View File

@@ -173,6 +173,7 @@ function FieldsSelectorContent({
{hasUnsavedChanges && (
<div className={styles.footer}>
<Button
size="md"
variant="outlined"
color="secondary"
onClick={handleDiscard}
@@ -181,6 +182,7 @@ function FieldsSelectorContent({
Discard
</Button>
<Button
size="md"
variant="solid"
color="primary"
onClick={handleSave}

View File

@@ -138,7 +138,6 @@ function OtherFields({
<span className={styles.fieldKey}>{attr.name}</span>
{!isAtLimit && (
<Button
className={cx(styles.addBtn, 'periscope-btn')}
variant="outlined"
color="secondary"
size="sm"

View File

@@ -111,14 +111,11 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
const lineChartRef = useRef<Chart>();
useImperativeHandle(
ref,
(): ToggleGraphProps => ({
toggleGraph(graphIndex: number, isVisible: boolean): void {
toggleGraph(graphIndex, isVisible, lineChartRef);
},
}),
);
useImperativeHandle(ref, (): ToggleGraphProps => ({
toggleGraph(graphIndex: number, isVisible: boolean): void {
toggleGraph(graphIndex, isVisible, lineChartRef);
},
}));
const getGridColor = useCallback(() => {
if (currentTheme === undefined) {

View File

@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui/sonner';
import { Button, Input } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { ToggleGroup } from '@signozhq/ui/toggle-group';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
@@ -102,10 +102,12 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
return (
<div className="feedback-modal-container">
<div className="feedback-modal-header">
<ToggleGroupSimple
<ToggleGroup
variant="outlined"
color="secondary"
size="sm"
type="single"
value={activeTab}
className="feedback-modal-tabs"
onChange={setActiveTab}
items={items}
/>

View File

@@ -1,7 +1,8 @@
.header-right-section-container {
display: flex;
align-items: center;
gap: 8px;
gap: var(--spacing-8);
padding-right: var(--spacing-8);
}
.header-ai-assistant-btn-container {
@@ -121,78 +122,26 @@
}
.feedback-modal-container {
.feedback-modal-tabs {
width: 100%;
.feedback-modal-tab-label {
display: flex;
align-items: center;
gap: 8px;
.ant-radio-button-wrapper {
flex: 1;
margin: 0px !important;
border: 1px solid var(--l1-border);
&:before {
display: none;
}
.ant-radio-button-checked {
background-color: var(--l3-background);
}
.tab-icon {
width: 6px;
height: 6px;
}
.feedback-modal-tab-label {
display: flex;
align-items: center;
gap: 8px;
.tab-icon {
width: 6px;
height: 6px;
}
.feedback-tab {
background-color: var(--danger-background);
}
.bug-tab {
background-color: var(--warning-background);
}
.feature-tab {
background-color: var(--primary-background);
}
.feedback-tab {
background-color: var(--danger-background);
}
.ant-tabs-nav-list {
.ant-tabs-tab {
padding: 6px 16px;
.bug-tab {
background-color: var(--warning-background);
}
border-radius: 2px;
background: var(--l2-background);
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
border: 1px solid var(--l1-border);
margin: 0 !important;
.ant-tabs-tab-btn {
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 166.667% */
letter-spacing: -0.06px;
}
&-active {
background: var(--l3-background);
color: var(--l1-foreground);
border-bottom: none !important;
.ant-tabs-tab-btn {
color: var(--l1-foreground);
}
}
}
.feature-tab {
background-color: var(--primary-background);
}
}

View File

@@ -2,7 +2,7 @@ import { useCallback, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { Dot } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import { Tooltip } from '@signozhq/ui/tooltip';
import Noz from 'components/Noz/Noz';
import { NOZ_TOOLTIP_TITLE } from 'components/Noz/Noz.constants';
import { Popover } from 'antd';
@@ -113,24 +113,26 @@ function HeaderRightSection({
</span>
) : null}
<TooltipSimple title={NOZ_TOOLTIP_TITLE}>
<Button
variant="solid"
color="secondary"
className="noz-wave"
onClick={handleOpenAIAssistant}
aria-label={
showHeaderPendingBadge
? pendingUserInputCount === 1
? 'Open Noz, 1 action needs your response'
: `Open Noz, ${pendingUserInputCount} actions need your response`
: 'Open Noz'
}
prefix={<Noz size={20} />}
>
<Typography.Text>Noz</Typography.Text>
</Button>
</TooltipSimple>
<span className="noz-wave">
<Tooltip title={NOZ_TOOLTIP_TITLE}>
<Button
size="md"
variant="ghost"
color="secondary"
onClick={handleOpenAIAssistant}
aria-label={
showHeaderPendingBadge
? pendingUserInputCount === 1
? 'Open Noz, 1 action needs your response'
: `Open Noz, ${pendingUserInputCount} actions need your response`
: 'Open Noz'
}
prefix={<Noz size={20} />}
>
<Typography.Text>Noz</Typography.Text>
</Button>
</Tooltip>
</span>
</div>
)}
@@ -147,13 +149,15 @@ function HeaderRightSection({
onOpenChange={handleOpenFeedbackModalChange}
>
<Button
variant="ghost"
size="icon"
className="share-feedback-btn"
color="primary"
variant="link"
size="md"
icon
aria-label="Feedback"
prefix={<SquarePen size={14} />}
onClick={handleOpenFeedbackModal}
/>
>
<SquarePen size={14} />
</Button>
</Popover>
)}
@@ -170,16 +174,19 @@ function HeaderRightSection({
onOpenChange={handleOpenAnnouncementsModalChange}
>
<Button
variant="ghost"
size="icon"
color="primary"
variant="link"
size="md"
icon
aria-label="Announcements"
prefix={<Inbox size={14} />}
onClick={(): void => {
logEvent('Announcements: Clicked', {
page: location.pathname,
});
}}
/>
>
<Inbox size={14} />
</Button>
</Popover>
)}
@@ -196,12 +203,15 @@ function HeaderRightSection({
onOpenChange={handleOpenShareURLModalChange}
>
<Button
variant="ghost"
size="icon"
color="primary"
variant="link"
size="md"
icon
aria-label="Share"
prefix={<Globe size={14} />}
onClick={handleOpenShareURLModal}
/>
>
<Globe size={14} />
</Button>
</Popover>
)}
</div>

View File

@@ -149,6 +149,9 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
<Info size={14} color={Color.BG_AMBER_600} />
)}
<Switch
color="primary"
textPlacement="right"
disabledTooltip="Please select / enter valid relative time to toggle."
value={enableAbsoluteTime}
disabled={!isValidateRelativeTime}
onChange={(): void => {
@@ -173,6 +176,8 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
</Typography.Text>
<div className="absolute-relative-time-toggler">
<Switch
color="primary"
textPlacement="right"
value={enableExtraOption}
onChange={(): void => setEnableExtraOption((prev) => !prev)}
/>

View File

@@ -69,23 +69,23 @@ describe('FeedbackModal', () => {
const user = userEvent.setup();
render(<FeedbackModal onClose={mockOnClose} />);
// Initially, feedback radio should be active
const feedbackRadio = screen.getByRole('radio', { name: 'Feedback' });
expect(feedbackRadio).toBeChecked();
// Initially, feedback button should be active
const feedbackButton = screen.getByRole('button', { name: 'Feedback' });
expect(feedbackButton).toHaveAttribute('aria-pressed', 'true');
const bugTab = screen.getByText('Report a bug');
await user.click(bugTab);
// Bug radio should now be active
const bugRadio = screen.getByRole('radio', { name: 'Report a bug' });
expect(bugRadio).toBeChecked();
// Bug button should now be active
const bugButton = screen.getByRole('button', { name: 'Report a bug' });
expect(bugButton).toHaveAttribute('aria-pressed', 'true');
const featureTab = screen.getByText('Feature request');
await user.click(featureTab);
// Feature radio should now be active
const featureRadio = screen.getByRole('radio', { name: 'Feature request' });
expect(featureRadio).toBeChecked();
// Feature button should now be active
const featureButton = screen.getByRole('button', { name: 'Feature request' });
expect(featureButton).toHaveAttribute('aria-pressed', 'true');
});
it('should update feedback text when typing in textarea', async () => {
@@ -133,9 +133,9 @@ describe('FeedbackModal', () => {
const bugTab = screen.getByText('Report a bug');
await user.click(bugTab);
// Verify bug report radio is now active
const bugRadio = screen.getByRole('radio', { name: 'Report a bug' });
expect(bugRadio).toBeChecked();
// Verify bug report button is now active
const bugButton = screen.getByRole('button', { name: 'Report a bug' });
expect(bugButton).toHaveAttribute('aria-pressed', 'true');
const textarea = screen.getByPlaceholderText('Write your feedback here...');
const submitButton = screen.getByRole('button', { name: /submit/i });
@@ -166,9 +166,9 @@ describe('FeedbackModal', () => {
const featureTab = screen.getByText('Feature request');
await user.click(featureTab);
// Verify feature request radio is now active
const featureRadio = screen.getByRole('radio', { name: 'Feature request' });
expect(featureRadio).toBeChecked();
// Verify feature request button is now active
const featureButton = screen.getByRole('button', { name: 'Feature request' });
expect(featureButton).toHaveAttribute('aria-pressed', 'true');
const textarea = screen.getByPlaceholderText('Write your feedback here...');
const submitButton = screen.getByRole('button', { name: /submit/i });
@@ -262,8 +262,8 @@ describe('FeedbackModal', () => {
);
expect(newTextArea).toHaveValue(''); // Should be empty
// Verify active radio is reset to default (Feedback radio)
const feedbackRadio = screen.getByRole('radio', { name: 'Feedback' });
expect(feedbackRadio).toBeChecked();
// Verify active button is reset to default (Feedback button)
const feedbackButton = screen.getByRole('button', { name: 'Feedback' });
expect(feedbackButton).toHaveAttribute('aria-pressed', 'true');
});
});

Some files were not shown because too many files have changed in this diff Show More