Compare commits

...

28 Commits

Author SHA1 Message Date
Vinicius Lourenço
2cd7285e3e chore(audit): address tinypool audit (#13203)
Some checks are pending
build-staging / staging (push) Blocked by required conditions
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
cacheci / tests (push) Waiting to run
Release Drafter / update_release_draft (push) Waiting to run
<!--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
326 changed files with 13321 additions and 2741 deletions

View File

@@ -2,25 +2,13 @@ name: e2eci
on:
pull_request:
types:
- opened
- synchronize
- reopened
- labeled
pull_request_target:
types:
- labeled
merge_group:
types:
- checks_requested
concurrency:
group: e2eci-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
changes:
if: github.event_name != 'merge_group'
runs-on: ubuntu-latest
outputs:
e2e: ${{ steps.changes.outputs.e2e }}
@@ -30,17 +18,21 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
LABELED: ${{ contains(github.event.pull_request.labels.*.name, 'safe-to-e2e') }}
FORCE: ${{ contains(github.event.pull_request.labels.*.name, 'safe-to-e2e') }}
run: |
files=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename')
if [ "$LABELED" = true ] || grep -qE '^(frontend|tests/e2e)/' <<< "$files"; then
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:
needs: changes
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'))) && needs.changes.outputs.e2e == 'true'
if: needs.changes.outputs.e2e == 'true'
runs-on: ubuntu-latest
steps:
- name: checkout
@@ -70,9 +62,7 @@ jobs:
- chromium
shard: [1, 2, 3, 4]
needs: changes
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'))) && needs.changes.outputs.e2e == 'true'
if: needs.changes.outputs.e2e == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
@@ -181,17 +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,
# including fork and dependabot PRs still waiting on safe-to-test.
# 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: |
github.event_name != 'merge_group' && (needs.changes.result != 'success' ||
(needs.changes.outputs.e2e == 'true' && (needs.fmtlint.result != 'success' || needs.test.result != 'success')))
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
echo "::error::e2e did not pass. Fork and dependabot PRs need the safe-to-test label."
echo "::error::e2e did not pass."
exit 1

View File

@@ -104,6 +104,7 @@ func runGenerateAuthz(_ context.Context) error {
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMetrics).String(): true,
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMeterMetrics).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceQuickFilter).String(): true,
coretypes.NewResourceRef(coretypes.ResourceMetaResourceSavedView).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:
@@ -8046,6 +8114,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:
@@ -14072,6 +14165,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
@@ -16064,10 +16218,8 @@ paths:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
- api_key: []
- tokenizer: []
summary: Get stats
tags:
- stats
@@ -18916,9 +19068,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: Run Infra Monitoring Setup Checks
tags:
- inframonitoring
@@ -18990,9 +19142,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Clusters for Infra Monitoring
tags:
- inframonitoring
@@ -19074,9 +19226,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List DaemonSets for Infra Monitoring
tags:
- inframonitoring
@@ -19152,9 +19304,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Deployments for Infra Monitoring
tags:
- inframonitoring
@@ -19220,9 +19372,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Hosts for Infra Monitoring
tags:
- inframonitoring
@@ -19302,9 +19454,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Jobs for Infra Monitoring
tags:
- inframonitoring
@@ -19383,9 +19535,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
@@ -19454,9 +19606,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Namespaces for Infra Monitoring
tags:
- inframonitoring
@@ -19529,9 +19681,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Nodes for Infra Monitoring
tags:
- inframonitoring
@@ -19605,9 +19757,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Pods for Infra Monitoring
tags:
- inframonitoring
@@ -19676,9 +19828,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List Volumes for Infra Monitoring
tags:
- inframonitoring
@@ -19754,9 +19906,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- VIEWER
- metrics:read
- tokenizer:
- VIEWER
- metrics:read
summary: List StatefulSets for Infra Monitoring
tags:
- inframonitoring
@@ -24924,6 +25076,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

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

@@ -204,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",

403
frontend/pnpm-lock.yaml generated
View File

@@ -487,14 +487,14 @@ importers:
specifier: 8.22.0
version: 8.22.0(prettier@3.8.3)(typescript@5.9.3)
oxfmt:
specifier: 0.54.0
version: 0.54.0
specifier: 0.73.0
version: 0.73.0
oxlint:
specifier: 1.69.0
version: 1.69.0(oxlint-tsgolint@0.23.0)
specifier: 1.88.0
version: 1.88.0(oxlint-tsgolint@7.0.2004)
oxlint-tsgolint:
specifier: 0.23.0
version: 0.23.0
specifier: 7.0.2004
version: 7.0.2004
postcss:
specifier: 8.5.26
version: 8.5.26
@@ -533,7 +533,7 @@ importers:
version: 1.6.0(react@18.2.0)
vite-plugin-checker:
specifier: 0.12.0
version: 0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.69.0(oxlint-tsgolint@0.23.0))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3)
version: 0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.88.0(oxlint-tsgolint@7.0.2004))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3)
vite-plugin-compression:
specifier: 0.5.1
version: 0.5.1(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))
@@ -2555,276 +2555,276 @@ packages:
cpu: [x64]
os: [win32]
'@oxfmt/binding-android-arm-eabi@0.54.0':
resolution: {integrity: sha512-NAtpl/SiaeU103e7/OmZw0MvUnsUUopW7hEm/ecegJg7YM0skQaA0IXEZoyTV6NUdiNPupdIUreRqUZTShbn/g==}
'@oxfmt/binding-android-arm-eabi@0.73.0':
resolution: {integrity: sha512-dhunAdMbi/4o0Y66/+Wl6rpsNqpvE4i26Qw6bB77A97L2lJoktWg6m+X9QOon8iOEEqzD9rEZqFi5uZ4oIzhCA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@oxfmt/binding-android-arm64@0.54.0':
resolution: {integrity: sha512-B4VZfBUlKK1rmMChsssNZbkZjE8+FzG3avMjGgMDwbGxXRoXkoeXiAZ+78Oa+eyDPHvDCiUb4zH/vmCOUSafLQ==}
'@oxfmt/binding-android-arm64@0.73.0':
resolution: {integrity: sha512-dBoypjC2/xeyKyIhN2waX0/XeoccH9k0lVVe+sGRc4X8HSJVRtqyZDoXdr7bGY9A75c51dX0z0dLDpGQKwCdCg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@oxfmt/binding-darwin-arm64@0.54.0':
resolution: {integrity: sha512-i02vF75b+ePsQP3tHqSxVYI5S6b8X/xqdPu7/mDHXtpgXLTYXi3jJmfHU0j+dnZZDKaYTx/ioCK7QYJmtiJR2g==}
'@oxfmt/binding-darwin-arm64@0.73.0':
resolution: {integrity: sha512-Tn7Y+qWIpCQK+qdYwBcn6YvqeBuPiuG3tcNlYg16a5QL2o3rSmrgBB1nCyYqo0uvc1LNrfQW9/CK2tzFuID/Fg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@oxfmt/binding-darwin-x64@0.54.0':
resolution: {integrity: sha512-8VMFvGvooXj7mswkbrhdVZ2/sgiDaBzWpkkbtO+qGDLV4EfJd67nQadHkQC0ZNbaWA9ajXfqI6i7PZLIeDzxEQ==}
'@oxfmt/binding-darwin-x64@0.73.0':
resolution: {integrity: sha512-yVb0E0tg/+9Xc9YJCBq66qF1DC/aN5mhb5nnHz0k4ikwkMANtSul2VPDSwjYdXR6/DFimc+pPUgLBqkIa5Ctuw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@oxfmt/binding-freebsd-x64@0.54.0':
resolution: {integrity: sha512-0cRHnp43WN1Jrc5s0BdbdKgR1XirdvHy7TAFi3JEsoEVQVJxTXMbpVd76sxXlgRswNMDhVFSJw+y7Eb8mEavFQ==}
'@oxfmt/binding-freebsd-x64@0.73.0':
resolution: {integrity: sha512-IKR4lFnBkYNL9Uqdxa5GMTQLvnLj+3sDwTvh2atHCzah0xeo+1xR5bigRU8uyXlOsA4VpXUn0UAimq4E4RBGmQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@oxfmt/binding-linux-arm-gnueabihf@0.54.0':
resolution: {integrity: sha512-JyQAk3hK/OEtup7Rw6kZwfdzbKqTVD5jXXb8Xpfay29suwZyfBDMVW/bj4RqEPySYWc6zCp198pOluf8n5uYzg==}
'@oxfmt/binding-linux-arm-gnueabihf@0.73.0':
resolution: {integrity: sha512-7qnZ4g0ZCO3aZevOOQpZIABYwwXJW1lLDo2elviGdPQ0i11255xKEMFsi97fC1/nauQFseCJfELUy/k2JoOP+g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxfmt/binding-linux-arm-musleabihf@0.54.0':
resolution: {integrity: sha512-qnvLatTpM8vtvjOfcckBOzJjk+n6ce/wwpP8OFeUrD5aNLYcKyWAitwj+Rk3PK9jGanbZvKsJnv14JGQ6XqFdw==}
'@oxfmt/binding-linux-arm-musleabihf@0.73.0':
resolution: {integrity: sha512-36eBico2j/WPB84gb4FYd/jjqGCKOwU4M+TWLBUIqlnzTozQueLy0joA5BwsSp2OZFVkOlpCwvA3ndPamdiyLw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxfmt/binding-linux-arm64-gnu@0.54.0':
resolution: {integrity: sha512-SMkhnCzIYZYDk9vw3W/80eeYKmrMpGF0Giuxt4HruFlCH7jEtnPeb3SdQKMfgYi/dgtaf+hZAb5XWPYnxqCQ3w==}
'@oxfmt/binding-linux-arm64-gnu@0.73.0':
resolution: {integrity: sha512-VZ3cSgCAT18knKo52DcACmAuIoqG6qVEyeZRFbJd0xmYQ8GyYjuBc6IAh6MZHijT6q7p4/HPI+6ZWsCuhdV6iw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-arm64-musl@0.54.0':
resolution: {integrity: sha512-QrwJlBFFKnxOd95TAaszpMbZBLzMoYMpGaQTZF8oibacnF5rv8l12IhILhQRPmksWiBqg0YSe2Mnl7ayeJAHSA==}
'@oxfmt/binding-linux-arm64-musl@0.73.0':
resolution: {integrity: sha512-8YVzH59XSHcqwXNgc5qK851zCqwpmBgOifARXEPPe5/xTsIbHceYoPZAyqfpYV0CfKmzSaEDBgYHFza4rhx+Zw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@oxfmt/binding-linux-ppc64-gnu@0.54.0':
resolution: {integrity: sha512-WILatiol/TUHTlhod7R09+7Az/XlhKwmY1MHfLZNmewltPWNN/EwxP2rQSHahibZ/cB8gmckEBjBOByD+5bYsQ==}
'@oxfmt/binding-linux-ppc64-gnu@0.73.0':
resolution: {integrity: sha512-MqRJnmCys0rwOgpWyEidSA7v/bvOqfNDJhPkQoR9mPVYrvSmMaW2Xhdpl28cbTA+brOM1is62GjIvJplVlUVpw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-riscv64-gnu@0.54.0':
resolution: {integrity: sha512-f05YMG4BH4G8S4ME6UM6fi1MnJ9094mrnvO5Pa4SJlMfWlUM+1/ZWMEF4NnjM7shZAvbHsHRuVYpUo0PHC4P9Q==}
'@oxfmt/binding-linux-riscv64-gnu@0.73.0':
resolution: {integrity: sha512-4t2F3SWJSK/O/kRRFnL0elJzn75fJGGu71gs1HtiWcS80SmJOpkHiwwLnTp9suGdns8VHEPxYS0GTcyVwHKWBA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-riscv64-musl@0.54.0':
resolution: {integrity: sha512-UfL+2hj1ClNqcCRT9s8vBU4axDpjxgVxX96G+9DYAYjoc5b0u15CJtn2jgsi9iM+EbGNc5CW1HVRgwVu76UsSA==}
'@oxfmt/binding-linux-riscv64-musl@0.73.0':
resolution: {integrity: sha512-D8b4I8I1kLdJQFwdFXNV+cwh3xWSiNKofjdGUk0nDpc0AHOds8T+poGbP9RWQMvgsvMb9OMgp/aV7ahBR37TbQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@oxfmt/binding-linux-s390x-gnu@0.54.0':
resolution: {integrity: sha512-3/XZe931Hka+J6NjnaqJzYpsWWxDTuRdUdwSQHnOuJEgbC+SehIMFJS8hsEjV7LBhVSL2OCnRLvbVW8O97XIyw==}
'@oxfmt/binding-linux-s390x-gnu@0.73.0':
resolution: {integrity: sha512-QrC1S5VL1+N5rvorB15jH7CPvjVHA7GyUI5oVEd8Bd9m5605RDBQYTxDQ920DuylRoZOh2V7/xXpfCtFUrojUQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-x64-gnu@0.54.0':
resolution: {integrity: sha512-Ik93RlObtu43GbxApafayFjwYE06L6Xr08cSwpBPYbDrLp2ReZx0Jm1DqwRyYRnukUJy+rK2WaEvUQOxdytU9Q==}
'@oxfmt/binding-linux-x64-gnu@0.73.0':
resolution: {integrity: sha512-dFcUKQPltCEPa+YlkF3zqufn6nJPZdG73LpnjBvZFFsU3laCMssDXSYB99mVaj43FqdEgjpz5TO6gbYXcczd9g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-x64-musl@0.54.0':
resolution: {integrity: sha512-yZcakmPlD86CNymknd7KfW+FH+qfbqJH+i0h69CYfV1+KMoVeM9UED+8+TDVoU4haxI0NxY7RPCvRLy3Sqd2Qg==}
'@oxfmt/binding-linux-x64-musl@0.73.0':
resolution: {integrity: sha512-LmQe+nV8e+eUwfPNCxDF+owbsg88toBiR/8rZRgVOeKQP3MaGefXZRvBoc1UG37iWEQ/9JlCXG9Mc4nf4N3agw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@oxfmt/binding-openharmony-arm64@0.54.0':
resolution: {integrity: sha512-GiVBZNnEZnKu00f1jTg49nomv187d0GQX+O+ocykoLeiaALuEO+swoTehHn9TehTfi7V8H0i0e/yvUjCqnwk1w==}
'@oxfmt/binding-openharmony-arm64@0.73.0':
resolution: {integrity: sha512-8weOr1OKknE0G2DbR6ejJUT9AOsoBU6cXlsetmbihHv1K+bK7qP61T6lJxY0k/aAO7GyOJiRGNlL+04r4eH8Xg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@oxfmt/binding-win32-arm64-msvc@0.54.0':
resolution: {integrity: sha512-J0SSB8Z1Fre2sxRolYcW6Rl1RQmKdQ2hnHyq4YJrfBRiXTObLw4DXnIVraM/UyqGqwOi7yTrQA4VT7DPxlHVKA==}
'@oxfmt/binding-win32-arm64-msvc@0.73.0':
resolution: {integrity: sha512-mZV+QxXYgb8E18gzAFItb937VIsE4dqWdStrD3tRRC52UubOYEuU+e7NaIqUsGdB5YsJqNeuck3Ue/af2r9wyA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@oxfmt/binding-win32-ia32-msvc@0.54.0':
resolution: {integrity: sha512-O61UDVj8zz6yXJjkHPf05VaMLOXmEF8P5kf/N0W7AQMmd6bcQogl+KJc7rMutKTL524oE9iH32JXZClBFmEQIg==}
'@oxfmt/binding-win32-ia32-msvc@0.73.0':
resolution: {integrity: sha512-QJPUsbgUb97ZPPMkFUq6VQq0fLhC4Np19KL9aB/qKVwXnpKE6MIiILXqzhKnQ/UbnEDjKJ7Xv1d6zH4bYPCUdQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ia32]
os: [win32]
'@oxfmt/binding-win32-x64-msvc@0.54.0':
resolution: {integrity: sha512-1MDpqJPiFqxWtIHas8vkb1VZ7f7eKyTffAwmO8isxQYMaG1OFKsH666BWLeXQLO+IWNfiMssLD55hbR1lIPTqg==}
'@oxfmt/binding-win32-x64-msvc@0.73.0':
resolution: {integrity: sha512-cGcze2fu+ykIaFtkEvZlkZ3HvnCLk+WISthtq99zWTWn0x13t5L9TxvGdLZMUMvtfYR+ICkiCwKnguVubdcw0w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
'@oxlint-tsgolint/darwin-arm64@0.23.0':
resolution: {integrity: sha512-gOs9PVr2wEg4ox9z0aJo+RKhhImW86YL5N6yav8BK/rgPsIrwN/igSZ+pbRr723NFvUNKde9fgMhRA6JrXAOZw==}
'@oxlint-tsgolint/darwin-arm64@7.0.2004':
resolution: {integrity: sha512-uJk3ttju7Lz+Nv8WG4CsLyewoL1qf/Ny8S8OksJ7Bm/F14Tc9gMfW6arM1PCc57IzH/X/I79YsCWTgxX1FKibQ==}
cpu: [arm64]
os: [darwin]
'@oxlint-tsgolint/darwin-x64@0.23.0':
resolution: {integrity: sha512-kjJ8B+7n4tB9VJdxS5A9GdJt6/bYpzbu4lXp2uO1S3sRmCB5gDEABlGoiePNApRWaW+xqL4b4xgiE727jSLhuA==}
'@oxlint-tsgolint/darwin-x64@7.0.2004':
resolution: {integrity: sha512-0BFfHTzDxNDFPcu0UZAXT6CyCJHAXM0dZveyDb4KEHa2qF/DSnOKOrmvK/wFuFol9kvijbGM9JfuOsor+zWXRw==}
cpu: [x64]
os: [darwin]
'@oxlint-tsgolint/linux-arm64@0.23.0':
resolution: {integrity: sha512-6dCZuKNu135seMXilkRk9SpCx6i1XgmiipYGalLij5WVRX6ZYS8c4xI7preN/zv9fCXhsQclTIMDu2Y/cytTjw==}
'@oxlint-tsgolint/linux-arm64@7.0.2004':
resolution: {integrity: sha512-TAehQc+qdRXyDN3nl/r/3/oPrBVrbtrDeGMkFyG6mf+XphpoJ2lAMVFLclyvrVZCw02RbKROjyw8h72XwQQHcw==}
cpu: [arm64]
os: [linux]
'@oxlint-tsgolint/linux-x64@0.23.0':
resolution: {integrity: sha512-3bdilnyA7kmSTjK27rvjIjSxL5SIg3wt7vwNiRkouWB83ytssyKnuGvxSYJxgMEmFpSutzaBzcCUM2jDtPGcgA==}
'@oxlint-tsgolint/linux-x64@7.0.2004':
resolution: {integrity: sha512-LPunZvm6ZbWPfHMAyfZf9ci3HHvNEzLejO8zdqO3GoVGoSEHqqufHUD7+OgeatVSaJP7uXDJ2HaNOHIoH7go4Q==}
cpu: [x64]
os: [linux]
'@oxlint-tsgolint/win32-arm64@0.23.0':
resolution: {integrity: sha512-j+OEp44SVYiQ+ZD+uttsX7u6L9SvmbbQ77SO1pSFCcJlsVMeCk8qZsjhKfGKuT/jIA+ipOJMVs/+pqUfObBWNw==}
'@oxlint-tsgolint/win32-arm64@7.0.2004':
resolution: {integrity: sha512-FVWplzDImWhiuawnIZVETaQecv4GWom6Jv4Fx2xj7hhXXDZ5okEgiw1gTCImHTIQsi2ycC5oSxVwrn69bMh7iw==}
cpu: [arm64]
os: [win32]
'@oxlint-tsgolint/win32-x64@0.23.0':
resolution: {integrity: sha512-5MyjFuqf+g8OUPJBSGWHJtmoWnzFJYyOg4To9WMQshZYEWig/vtu7JtJ03VWnzHv9LJkAUeApY0gVCOywFR/iQ==}
'@oxlint-tsgolint/win32-x64@7.0.2004':
resolution: {integrity: sha512-7PsxuUx11Ry9DYTWcljTzRbmZY9B6O9fFFfZV+aodQEMy6pfRT3fhoQRv9PtGN/89z1pPz3nFKrsFm6WqNtX7w==}
cpu: [x64]
os: [win32]
'@oxlint/binding-android-arm-eabi@1.69.0':
resolution: {integrity: sha512-DKQQbD5cZ/MYfDgDI7YGyGD9FSxABlsBsYFo5p26lloob543tP9+4N3guwdXIYJN+7HSZxLe8YJuwcOWw5qnHg==}
'@oxlint/binding-android-arm-eabi@1.88.0':
resolution: {integrity: sha512-k6Fgkf86ubfeH4GvqYsRolfuVAp0pfYVZvifSlXNHPdMaWUOfxUrmLEeDD7hF6U2oJuhsVxhSWq86/xCJ+9MmA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@oxlint/binding-android-arm64@1.69.0':
resolution: {integrity: sha512-lEhb+I5pr4inux+JFwfCa1HRq3Os7NirEFQ0H1I35SVEHPm6byX0Ah47xmRha3qi6LAkxUcxViL8o/9PivjzBg==}
'@oxlint/binding-android-arm64@1.88.0':
resolution: {integrity: sha512-AsIyhl9o1ejwtac7NMbhKpMdSml6NWR0ljKjZERxC8OHYDAW8o/Om9n1QHVLi4VCEUo42Zte2/Op9kyoGpJZMg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@oxlint/binding-darwin-arm64@1.69.0':
resolution: {integrity: sha512-GY2YE8lOZW59BW1Ia1y+1gR0XyjrZRvVWHAr8LGeGhYHE0OQJ/7cRKXTkx1P+E9/6awEc3SX8a68SFTjh/E//A==}
'@oxlint/binding-darwin-arm64@1.88.0':
resolution: {integrity: sha512-qgBjB7eEKcCB4RqjXGuoSZq74g0wD09fWFJ+HWcCJcKaga8TeUHtb+BY0SsxdleSLBgx4RZ7qmgo0EzqEq7JoQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@oxlint/binding-darwin-x64@1.69.0':
resolution: {integrity: sha512-ax1oZnOjHX3LB7myQyHEaQkDwfLb6str3/nSP6O7EVUviQGNkEGzGV0EqcBJWK+Ufwx0l4xPgyYayurvhAdl2Q==}
'@oxlint/binding-darwin-x64@1.88.0':
resolution: {integrity: sha512-sCz3X5Mw/xdzEzn4fsCt1Iq8NKJnGNfhR+b9B5k+4DysYAHsmIhYoocnBPiQ9kvY3+pUun2A0Z8Vtun/Nv6YoQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@oxlint/binding-freebsd-x64@1.69.0':
resolution: {integrity: sha512-kHWeHv4g2h8NY+mpCxzCtY4uerMJWTN/TSnNj1CPbakFpHEJ6cTya2wWV0pDSYWOJ2+0UiEbhn3AtXxHtsnKjg==}
'@oxlint/binding-freebsd-x64@1.88.0':
resolution: {integrity: sha512-xTOdYOIqCufgIj0knvxu86FBfovP6QBiD7ngNva01gb8xPQCD5gXkE5APGczH35zStim1O3zqvFbLQmY85xXRg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@oxlint/binding-linux-arm-gnueabihf@1.69.0':
resolution: {integrity: sha512-gq84vM1a1oEehXo27YCDzGVcxPsZDI1yswZwz2Da1/cbnWtrL16XZZnz0G/+gIU8edtHpfjxq5c+vWEHqJfWoQ==}
'@oxlint/binding-linux-arm-gnueabihf@1.88.0':
resolution: {integrity: sha512-BLadtw88NdYJBrAtC1dYzrPfSP2d5zgx6iuZycdD0YmZBJ2Nqbit9Z4PSaIb6gxK25dweArGMPbOmq/0ugwUPg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxlint/binding-linux-arm-musleabihf@1.69.0':
resolution: {integrity: sha512-kIqEa98JQ0VRyrcncxA417m2AzasqTlD+FyVT1AksjvjkqQcvm7pBWYvoW3/mpyOP2XYvi5nSCCTIe6De1yu5g==}
'@oxlint/binding-linux-arm-musleabihf@1.88.0':
resolution: {integrity: sha512-wILSczl/DaZsCcf7j7tvNbssp8hxEHIJxrv4j7+JjI3TAL1U/JTWbeUtoJJC/vo8+2hkTmHLYoKJjmiNbijdXw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxlint/binding-linux-arm64-gnu@1.69.0':
resolution: {integrity: sha512-j+xYiXozxGWx2cpjCrwwGR4awTxPFsRv3JZrv23RCogEPMc4R7UqjHW47p/RG0aRlbWiROCJ8coUfCwy0dvzHA==}
'@oxlint/binding-linux-arm64-gnu@1.88.0':
resolution: {integrity: sha512-jutT2Hy8JCuZuMwIFHo2HtyxfOfBJHyQxlBHBK1SL9W2f8n/WhjAP0FnDMz9d/mjhv9+QA0mx5AdQ9fDjsrFFg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-arm64-musl@1.69.0':
resolution: {integrity: sha512-xEPpNppTfN1l/nM7gYSf9iocscu/as+p/7vxkLeLEKnYU+09Dm+5V6IhDYDh+Uz6FajEupWwCLt5SOG0y1PCKg==}
'@oxlint/binding-linux-arm64-musl@1.88.0':
resolution: {integrity: sha512-8N8iGV9hh4Lkeuq7QsEkjoXNRRflqR1G2qyoGYXcA0k5qllrdqmfylKEXyHIVERxqNDyMO4StqDQ0uLxXcBQAg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@oxlint/binding-linux-ppc64-gnu@1.69.0':
resolution: {integrity: sha512-Ug0+eU7HJBlek+SjklYH62IlOMirEJsdxpihH0kSqX0XdrDD4NdHpQc10fK1JC35yn6KrrcN+uYzlHD38XAf8Q==}
'@oxlint/binding-linux-ppc64-gnu@1.88.0':
resolution: {integrity: sha512-5ydcQt3znNQ/rGEWYRvpS2cK9+T18P2VYgnyJ+eloEAwi9YUmsXy5VGrRqNWIK6qzWwHGnEE3ojVqqQNo+f0hA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-riscv64-gnu@1.69.0':
resolution: {integrity: sha512-iEyI3GIg0l/s3G4qy2TlaaWKdzj4PJJStwtlocpDTC00PY9hZueotf6OKUj9+yfQh0lrpBW/pLMgTztbAHKJEg==}
'@oxlint/binding-linux-riscv64-gnu@1.88.0':
resolution: {integrity: sha512-iGx0uvFT54b/5DPJdEBCxYIrVB3v0uTy+JhRh3CyEp8/22BXDkG8rffkFwREdLsdj9mOrPbJYVOZw8ipcBuzjw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-riscv64-musl@1.69.0':
resolution: {integrity: sha512-NjHjpiI4WIKSMwuoJSZi5VToPeoYOS1FR52HLIDG6lidMdqquusgtODb4iLk0+lb1q3Z0nv2/aPRcC/olmpQGg==}
'@oxlint/binding-linux-riscv64-musl@1.88.0':
resolution: {integrity: sha512-Wt47mKMWelVjRjuR7BCFxXkB0m79K1sTEbqMHRWciKUgo5iv7sWsF4RXSOyFYHD9zerNrkRZblnN273rhwOA1Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@oxlint/binding-linux-s390x-gnu@1.69.0':
resolution: {integrity: sha512-Ai/prDewoItkDXbp38gwGZi41DycZbUTZJ3UidwoHgQC0/DaqC2TGdtBTQLJ6hSD+SAxASzh8+/eSBPmxfOacA==}
'@oxlint/binding-linux-s390x-gnu@1.88.0':
resolution: {integrity: sha512-g3L12KCS4+1gtUt3RbJ4F+Yxx4LNmmZLEPfhd8rAHEJjJk2lAgNfnjGoP/phJyhG5WS6VbNYbJtadJelNfvTMA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-x64-gnu@1.69.0':
resolution: {integrity: sha512-Gt3KHgp46mRKz4sJeaASmKvD8ayXookRw07RMf+NowhEztGGDZ7VrXpoW96XuKJLjFukWizOFVNjmYb/u7caNQ==}
'@oxlint/binding-linux-x64-gnu@1.88.0':
resolution: {integrity: sha512-og/XHVDOD9ZJgRiZkT9DgzQd4sr+JZW7e4l17Q/jVMZiIbeEd4uIu9iAoUbTF1gIvVZoRc4QDOxyVUvQGp4WHg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-x64-musl@1.69.0':
resolution: {integrity: sha512-7tQhJ2+p/oHv1zcfnjYI7YVzC/7iBaVOfIvFYtxdJ5F45mWgEdrCyXZXZGfiLey5t/5JhOhsaMnnv1kAzckd7g==}
'@oxlint/binding-linux-x64-musl@1.88.0':
resolution: {integrity: sha512-EmhBsp+xuKb6YbZNEMLBo80Xw71AZOIm18do8ni/GLX/Lw1XzNhVYpbxO1KasYtoAvrGtCOdCPULRlIE0bPbTQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@oxlint/binding-openharmony-arm64@1.69.0':
resolution: {integrity: sha512-vmWz6TKp/3hfA4lksR0zHBv/6xuX1jhym6eqOjdH2DXsDDHZWcp2f0KG0VCAnlVbIrjk29G4wAWMXb/Hn1YobA==}
'@oxlint/binding-openharmony-arm64@1.88.0':
resolution: {integrity: sha512-DBUcx/KwYdg1DAz+G01P/GFv512J04EsZadbHptx2FTpWT/MWRg21ZToNHt9pnsfQRBQ3et/g+vqzbvrdAVbuA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@oxlint/binding-win32-arm64-msvc@1.69.0':
resolution: {integrity: sha512-9RExaLgmaw6IoIkU9cTpT71mLfI0xZ86iZH8x518LVsOkjquJMYqb9P7KpC8lgd1t0Dxs41p2pxynq4XR3Ttzw==}
'@oxlint/binding-win32-arm64-msvc@1.88.0':
resolution: {integrity: sha512-xeVd0n4XG5HvynxfYvXi6uB+JvZbVZataowPS8cYpMbYg9mwE4sSyElFQd4LVJFifq8bRLDSR18XgEildmcXAg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@oxlint/binding-win32-ia32-msvc@1.69.0':
resolution: {integrity: sha512-1907kRPF8/PrcIw1E7LMs9JbVrpgnt/MvFdss3an8oDkYNAACXzTntV3t3869ZZhMZxb2AzRGbz1pA/jdFatXA==}
'@oxlint/binding-win32-ia32-msvc@1.88.0':
resolution: {integrity: sha512-MA6iOw//amleaz2mVMsZrA7NHlEexjTrhIxSMFCXQi6IUuQO+3Od00GZhr2iCYw6RFzMW36jkKK/WHBXMsoQAw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ia32]
os: [win32]
'@oxlint/binding-win32-x64-msvc@1.69.0':
resolution: {integrity: sha512-w8SOXv3mT9Fi6jY8OXdXCfnvX/3KNLXGNr4HEz2TA7S4Mv/PYAOmpB8y/ge40mxvBMgGNaSaaDwZpAsQn7HtWA==}
'@oxlint/binding-win32-x64-msvc@1.88.0':
resolution: {integrity: sha512-3Sz6rVWe8Dy+ufsXurFN1GX2dX43F/lQMSLelK9gFjiba7nHvLlqlhR5cHlWVnCpSqUY1hN9pyCX9XToe6ve6g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
@@ -7785,29 +7785,32 @@ packages:
oxc-resolver@11.24.2:
resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==}
oxfmt@0.54.0:
resolution: {integrity: sha512-DjnMwn7smSLF+Mc2+pRItnuPftm/dkUFpY/d4+33y9TfKrsHZo8GLhmUg9BrOIUEy94Rlom1Q11N6vuhE+e0oQ==}
oxfmt@0.73.0:
resolution: {integrity: sha512-Ke4xbpuzqIBP4bkd48q815RhqWNJqOBzxVA9199M4qcF0y0LtpUv7UTg1dJ5ea6jw0RBUDZvX3qOUMNmmxfxbw==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@astrojs/compiler-rs': ^0.4.0 || ^0.5.0
svelte: ^5.0.0
vite-plus: '*'
peerDependenciesMeta:
'@astrojs/compiler-rs':
optional: true
svelte:
optional: true
vite-plus:
optional: true
oxlint-tsgolint@0.23.0:
resolution: {integrity: sha512-3mBv3CoPbh8dFbzfDGIWa2ytZjn2v+3EX4aKRXjIhsoGFzG8GCjfRirz3rwZf1wYbZzsNLTSgpw8VjQuWdp/jA==}
oxlint-tsgolint@7.0.2004:
resolution: {integrity: sha512-vQ9WDF3fiZJvRdSDosNOflerOCOpwol6vWzVDxW76RcU19cjTh7engvkHLvmv+tzedR8fAiJUVUGr6IVZdkzXQ==}
hasBin: true
oxlint@1.69.0:
resolution: {integrity: sha512-ypZkK/aDc5NQV8zIR6s2H2Tl3aNW8FmJ1m9+2qsaYuRenl8vgnHNCGwTHviWJdUQzglOlHFchgopdtGhSy17Rw==}
oxlint@1.88.0:
resolution: {integrity: sha512-uIcFgw8BmqvFRGzH5+r7d2zJMEexwNSrEmM6WQNnGh3VkO0XYTEFoyxyNE6tjKMUib9Cue4XJA2DoWruO5D6aw==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
oxlint-tsgolint: '>=0.22.1'
oxlint-tsgolint: '>=7.0.2003'
vite-plus: '*'
peerDependenciesMeta:
oxlint-tsgolint:
@@ -9399,8 +9402,8 @@ packages:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
tinypool@2.1.0:
resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==}
tinypool@2.2.0:
resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==}
engines: {node: ^20.0.0 || >=22.0.0}
tinyrainbow@2.0.0:
@@ -12469,136 +12472,136 @@ snapshots:
'@oxc-resolver/binding-win32-x64-msvc@11.24.2':
optional: true
'@oxfmt/binding-android-arm-eabi@0.54.0':
'@oxfmt/binding-android-arm-eabi@0.73.0':
optional: true
'@oxfmt/binding-android-arm64@0.54.0':
'@oxfmt/binding-android-arm64@0.73.0':
optional: true
'@oxfmt/binding-darwin-arm64@0.54.0':
'@oxfmt/binding-darwin-arm64@0.73.0':
optional: true
'@oxfmt/binding-darwin-x64@0.54.0':
'@oxfmt/binding-darwin-x64@0.73.0':
optional: true
'@oxfmt/binding-freebsd-x64@0.54.0':
'@oxfmt/binding-freebsd-x64@0.73.0':
optional: true
'@oxfmt/binding-linux-arm-gnueabihf@0.54.0':
'@oxfmt/binding-linux-arm-gnueabihf@0.73.0':
optional: true
'@oxfmt/binding-linux-arm-musleabihf@0.54.0':
'@oxfmt/binding-linux-arm-musleabihf@0.73.0':
optional: true
'@oxfmt/binding-linux-arm64-gnu@0.54.0':
'@oxfmt/binding-linux-arm64-gnu@0.73.0':
optional: true
'@oxfmt/binding-linux-arm64-musl@0.54.0':
'@oxfmt/binding-linux-arm64-musl@0.73.0':
optional: true
'@oxfmt/binding-linux-ppc64-gnu@0.54.0':
'@oxfmt/binding-linux-ppc64-gnu@0.73.0':
optional: true
'@oxfmt/binding-linux-riscv64-gnu@0.54.0':
'@oxfmt/binding-linux-riscv64-gnu@0.73.0':
optional: true
'@oxfmt/binding-linux-riscv64-musl@0.54.0':
'@oxfmt/binding-linux-riscv64-musl@0.73.0':
optional: true
'@oxfmt/binding-linux-s390x-gnu@0.54.0':
'@oxfmt/binding-linux-s390x-gnu@0.73.0':
optional: true
'@oxfmt/binding-linux-x64-gnu@0.54.0':
'@oxfmt/binding-linux-x64-gnu@0.73.0':
optional: true
'@oxfmt/binding-linux-x64-musl@0.54.0':
'@oxfmt/binding-linux-x64-musl@0.73.0':
optional: true
'@oxfmt/binding-openharmony-arm64@0.54.0':
'@oxfmt/binding-openharmony-arm64@0.73.0':
optional: true
'@oxfmt/binding-win32-arm64-msvc@0.54.0':
'@oxfmt/binding-win32-arm64-msvc@0.73.0':
optional: true
'@oxfmt/binding-win32-ia32-msvc@0.54.0':
'@oxfmt/binding-win32-ia32-msvc@0.73.0':
optional: true
'@oxfmt/binding-win32-x64-msvc@0.54.0':
'@oxfmt/binding-win32-x64-msvc@0.73.0':
optional: true
'@oxlint-tsgolint/darwin-arm64@0.23.0':
'@oxlint-tsgolint/darwin-arm64@7.0.2004':
optional: true
'@oxlint-tsgolint/darwin-x64@0.23.0':
'@oxlint-tsgolint/darwin-x64@7.0.2004':
optional: true
'@oxlint-tsgolint/linux-arm64@0.23.0':
'@oxlint-tsgolint/linux-arm64@7.0.2004':
optional: true
'@oxlint-tsgolint/linux-x64@0.23.0':
'@oxlint-tsgolint/linux-x64@7.0.2004':
optional: true
'@oxlint-tsgolint/win32-arm64@0.23.0':
'@oxlint-tsgolint/win32-arm64@7.0.2004':
optional: true
'@oxlint-tsgolint/win32-x64@0.23.0':
'@oxlint-tsgolint/win32-x64@7.0.2004':
optional: true
'@oxlint/binding-android-arm-eabi@1.69.0':
'@oxlint/binding-android-arm-eabi@1.88.0':
optional: true
'@oxlint/binding-android-arm64@1.69.0':
'@oxlint/binding-android-arm64@1.88.0':
optional: true
'@oxlint/binding-darwin-arm64@1.69.0':
'@oxlint/binding-darwin-arm64@1.88.0':
optional: true
'@oxlint/binding-darwin-x64@1.69.0':
'@oxlint/binding-darwin-x64@1.88.0':
optional: true
'@oxlint/binding-freebsd-x64@1.69.0':
'@oxlint/binding-freebsd-x64@1.88.0':
optional: true
'@oxlint/binding-linux-arm-gnueabihf@1.69.0':
'@oxlint/binding-linux-arm-gnueabihf@1.88.0':
optional: true
'@oxlint/binding-linux-arm-musleabihf@1.69.0':
'@oxlint/binding-linux-arm-musleabihf@1.88.0':
optional: true
'@oxlint/binding-linux-arm64-gnu@1.69.0':
'@oxlint/binding-linux-arm64-gnu@1.88.0':
optional: true
'@oxlint/binding-linux-arm64-musl@1.69.0':
'@oxlint/binding-linux-arm64-musl@1.88.0':
optional: true
'@oxlint/binding-linux-ppc64-gnu@1.69.0':
'@oxlint/binding-linux-ppc64-gnu@1.88.0':
optional: true
'@oxlint/binding-linux-riscv64-gnu@1.69.0':
'@oxlint/binding-linux-riscv64-gnu@1.88.0':
optional: true
'@oxlint/binding-linux-riscv64-musl@1.69.0':
'@oxlint/binding-linux-riscv64-musl@1.88.0':
optional: true
'@oxlint/binding-linux-s390x-gnu@1.69.0':
'@oxlint/binding-linux-s390x-gnu@1.88.0':
optional: true
'@oxlint/binding-linux-x64-gnu@1.69.0':
'@oxlint/binding-linux-x64-gnu@1.88.0':
optional: true
'@oxlint/binding-linux-x64-musl@1.69.0':
'@oxlint/binding-linux-x64-musl@1.88.0':
optional: true
'@oxlint/binding-openharmony-arm64@1.69.0':
'@oxlint/binding-openharmony-arm64@1.88.0':
optional: true
'@oxlint/binding-win32-arm64-msvc@1.69.0':
'@oxlint/binding-win32-arm64-msvc@1.88.0':
optional: true
'@oxlint/binding-win32-ia32-msvc@1.69.0':
'@oxlint/binding-win32-ia32-msvc@1.88.0':
optional: true
'@oxlint/binding-win32-x64-msvc@1.69.0':
'@oxlint/binding-win32-x64-msvc@1.88.0':
optional: true
'@parcel/watcher-android-arm64@2.5.1':
@@ -18323,61 +18326,61 @@ snapshots:
'@oxc-resolver/binding-win32-arm64-msvc': 11.24.2
'@oxc-resolver/binding-win32-x64-msvc': 11.24.2
oxfmt@0.54.0:
oxfmt@0.73.0:
dependencies:
tinypool: 2.1.0
tinypool: 2.2.0
optionalDependencies:
'@oxfmt/binding-android-arm-eabi': 0.54.0
'@oxfmt/binding-android-arm64': 0.54.0
'@oxfmt/binding-darwin-arm64': 0.54.0
'@oxfmt/binding-darwin-x64': 0.54.0
'@oxfmt/binding-freebsd-x64': 0.54.0
'@oxfmt/binding-linux-arm-gnueabihf': 0.54.0
'@oxfmt/binding-linux-arm-musleabihf': 0.54.0
'@oxfmt/binding-linux-arm64-gnu': 0.54.0
'@oxfmt/binding-linux-arm64-musl': 0.54.0
'@oxfmt/binding-linux-ppc64-gnu': 0.54.0
'@oxfmt/binding-linux-riscv64-gnu': 0.54.0
'@oxfmt/binding-linux-riscv64-musl': 0.54.0
'@oxfmt/binding-linux-s390x-gnu': 0.54.0
'@oxfmt/binding-linux-x64-gnu': 0.54.0
'@oxfmt/binding-linux-x64-musl': 0.54.0
'@oxfmt/binding-openharmony-arm64': 0.54.0
'@oxfmt/binding-win32-arm64-msvc': 0.54.0
'@oxfmt/binding-win32-ia32-msvc': 0.54.0
'@oxfmt/binding-win32-x64-msvc': 0.54.0
'@oxfmt/binding-android-arm-eabi': 0.73.0
'@oxfmt/binding-android-arm64': 0.73.0
'@oxfmt/binding-darwin-arm64': 0.73.0
'@oxfmt/binding-darwin-x64': 0.73.0
'@oxfmt/binding-freebsd-x64': 0.73.0
'@oxfmt/binding-linux-arm-gnueabihf': 0.73.0
'@oxfmt/binding-linux-arm-musleabihf': 0.73.0
'@oxfmt/binding-linux-arm64-gnu': 0.73.0
'@oxfmt/binding-linux-arm64-musl': 0.73.0
'@oxfmt/binding-linux-ppc64-gnu': 0.73.0
'@oxfmt/binding-linux-riscv64-gnu': 0.73.0
'@oxfmt/binding-linux-riscv64-musl': 0.73.0
'@oxfmt/binding-linux-s390x-gnu': 0.73.0
'@oxfmt/binding-linux-x64-gnu': 0.73.0
'@oxfmt/binding-linux-x64-musl': 0.73.0
'@oxfmt/binding-openharmony-arm64': 0.73.0
'@oxfmt/binding-win32-arm64-msvc': 0.73.0
'@oxfmt/binding-win32-ia32-msvc': 0.73.0
'@oxfmt/binding-win32-x64-msvc': 0.73.0
oxlint-tsgolint@0.23.0:
oxlint-tsgolint@7.0.2004:
optionalDependencies:
'@oxlint-tsgolint/darwin-arm64': 0.23.0
'@oxlint-tsgolint/darwin-x64': 0.23.0
'@oxlint-tsgolint/linux-arm64': 0.23.0
'@oxlint-tsgolint/linux-x64': 0.23.0
'@oxlint-tsgolint/win32-arm64': 0.23.0
'@oxlint-tsgolint/win32-x64': 0.23.0
'@oxlint-tsgolint/darwin-arm64': 7.0.2004
'@oxlint-tsgolint/darwin-x64': 7.0.2004
'@oxlint-tsgolint/linux-arm64': 7.0.2004
'@oxlint-tsgolint/linux-x64': 7.0.2004
'@oxlint-tsgolint/win32-arm64': 7.0.2004
'@oxlint-tsgolint/win32-x64': 7.0.2004
oxlint@1.69.0(oxlint-tsgolint@0.23.0):
oxlint@1.88.0(oxlint-tsgolint@7.0.2004):
optionalDependencies:
'@oxlint/binding-android-arm-eabi': 1.69.0
'@oxlint/binding-android-arm64': 1.69.0
'@oxlint/binding-darwin-arm64': 1.69.0
'@oxlint/binding-darwin-x64': 1.69.0
'@oxlint/binding-freebsd-x64': 1.69.0
'@oxlint/binding-linux-arm-gnueabihf': 1.69.0
'@oxlint/binding-linux-arm-musleabihf': 1.69.0
'@oxlint/binding-linux-arm64-gnu': 1.69.0
'@oxlint/binding-linux-arm64-musl': 1.69.0
'@oxlint/binding-linux-ppc64-gnu': 1.69.0
'@oxlint/binding-linux-riscv64-gnu': 1.69.0
'@oxlint/binding-linux-riscv64-musl': 1.69.0
'@oxlint/binding-linux-s390x-gnu': 1.69.0
'@oxlint/binding-linux-x64-gnu': 1.69.0
'@oxlint/binding-linux-x64-musl': 1.69.0
'@oxlint/binding-openharmony-arm64': 1.69.0
'@oxlint/binding-win32-arm64-msvc': 1.69.0
'@oxlint/binding-win32-ia32-msvc': 1.69.0
'@oxlint/binding-win32-x64-msvc': 1.69.0
oxlint-tsgolint: 0.23.0
'@oxlint/binding-android-arm-eabi': 1.88.0
'@oxlint/binding-android-arm64': 1.88.0
'@oxlint/binding-darwin-arm64': 1.88.0
'@oxlint/binding-darwin-x64': 1.88.0
'@oxlint/binding-freebsd-x64': 1.88.0
'@oxlint/binding-linux-arm-gnueabihf': 1.88.0
'@oxlint/binding-linux-arm-musleabihf': 1.88.0
'@oxlint/binding-linux-arm64-gnu': 1.88.0
'@oxlint/binding-linux-arm64-musl': 1.88.0
'@oxlint/binding-linux-ppc64-gnu': 1.88.0
'@oxlint/binding-linux-riscv64-gnu': 1.88.0
'@oxlint/binding-linux-riscv64-musl': 1.88.0
'@oxlint/binding-linux-s390x-gnu': 1.88.0
'@oxlint/binding-linux-x64-gnu': 1.88.0
'@oxlint/binding-linux-x64-musl': 1.88.0
'@oxlint/binding-openharmony-arm64': 1.88.0
'@oxlint/binding-win32-arm64-msvc': 1.88.0
'@oxlint/binding-win32-ia32-msvc': 1.88.0
'@oxlint/binding-win32-x64-msvc': 1.88.0
oxlint-tsgolint: 7.0.2004
p-limit@2.3.0:
dependencies:
@@ -20206,7 +20209,7 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
tinypool@2.1.0: {}
tinypool@2.2.0: {}
tinyrainbow@2.0.0: {}
@@ -20588,7 +20591,7 @@ snapshots:
unist-util-stringify-position: 4.0.0
vfile-message: 4.0.2
vite-plugin-checker@0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.69.0(oxlint-tsgolint@0.23.0))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3):
vite-plugin-checker@0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.88.0(oxlint-tsgolint@7.0.2004))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3):
dependencies:
'@babel/code-frame': 7.29.0
chokidar: 4.0.3
@@ -20603,7 +20606,7 @@ snapshots:
eslint: 10.2.1(jiti@2.6.1)
meow: 13.2.0
optionator: 0.9.4
oxlint: 1.69.0(oxlint-tsgolint@0.23.0)
oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)
stylelint: 17.15.0(typescript@5.9.3)
typescript: 5.9.3

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', () => {
@@ -1635,11 +1632,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

@@ -9,12 +9,14 @@ describe('ErrorResponseHandler', () => {
.spyOn(console, 'error')
.mockImplementation(() => undefined);
expect(ErrorResponseHandler(new CanceledError() as AxiosError)).toEqual({
statusCode: 500,
payload: null,
error: 'Something went wrong',
message: null,
});
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
*/
@@ -3080,8 +3130,7 @@ export type AuthtypesRoleMappingDTOGroupMappingsAnyOf = {
/**
* @nullable
*/
export type AuthtypesRoleMappingDTOGroupMappings =
AuthtypesRoleMappingDTOGroupMappingsAnyOf | null;
export type AuthtypesRoleMappingDTOGroupMappings = AuthtypesRoleMappingDTOGroupMappingsAnyOf | null;
export interface AuthtypesRoleMappingDTO {
/**
@@ -3692,8 +3741,7 @@ export type CloudintegrationtypesAgentReportDTODataAnyOf = {
/**
* @nullable
*/
export type CloudintegrationtypesAgentReportDTOData =
CloudintegrationtypesAgentReportDTODataAnyOf | null;
export type CloudintegrationtypesAgentReportDTOData = CloudintegrationtypesAgentReportDTODataAnyOf | null;
/**
* @nullable
@@ -4179,8 +4227,7 @@ export type CloudintegrationtypesPostableAgentCheckInDTODataAnyOf = {
/**
* @nullable
*/
export type CloudintegrationtypesPostableAgentCheckInDTOData =
CloudintegrationtypesPostableAgentCheckInDTODataAnyOf | null;
export type CloudintegrationtypesPostableAgentCheckInDTOData = CloudintegrationtypesPostableAgentCheckInDTODataAnyOf | null;
export interface CloudintegrationtypesPostableAgentCheckInDTO {
/**
@@ -4422,6 +4469,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;
/**
@@ -4868,8 +4957,7 @@ export type DashboardtypesLegendDTOCustomColorsAnyOf = {
/**
* @nullable
*/
export type DashboardtypesLegendDTOCustomColors =
DashboardtypesLegendDTOCustomColorsAnyOf | null;
export type DashboardtypesLegendDTOCustomColors = DashboardtypesLegendDTOCustomColorsAnyOf | null;
export interface DashboardtypesLegendDTO {
/**
@@ -5461,8 +5549,7 @@ export type DashboardtypesTableFormattingDTOColumnUnitsAnyOf = {
/**
* @nullable
*/
export type DashboardtypesTableFormattingDTOColumnUnits =
DashboardtypesTableFormattingDTOColumnUnitsAnyOf | null;
export type DashboardtypesTableFormattingDTOColumnUnits = DashboardtypesTableFormattingDTOColumnUnitsAnyOf | null;
export interface DashboardtypesTableFormattingDTO {
/**
@@ -6777,8 +6864,7 @@ export type FeaturetypesGettableFeatureDTOVariantsAnyOf = {
/**
* @nullable
*/
export type FeaturetypesGettableFeatureDTOVariants =
FeaturetypesGettableFeatureDTOVariantsAnyOf | null;
export type FeaturetypesGettableFeatureDTOVariants = FeaturetypesGettableFeatureDTOVariantsAnyOf | null;
export interface FeaturetypesGettableFeatureDTO {
/**
@@ -7256,8 +7342,7 @@ export type InframonitoringtypesClusterRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesClusterRecordDTOMeta =
InframonitoringtypesClusterRecordDTOMetaAnyOf | null;
export type InframonitoringtypesClusterRecordDTOMeta = InframonitoringtypesClusterRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesNodeCountsByReadinessDTO {
/**
@@ -7530,8 +7615,7 @@ export type InframonitoringtypesContainerRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesContainerRecordDTOMeta =
InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
export type InframonitoringtypesContainerRecordDTOMeta = InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesContainerRecordDTO {
containerCountsByReady: InframonitoringtypesContainerCountsByReadyDTO;
@@ -7622,8 +7706,7 @@ export type InframonitoringtypesDaemonSetRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesDaemonSetRecordDTOMeta =
InframonitoringtypesDaemonSetRecordDTOMetaAnyOf | null;
export type InframonitoringtypesDaemonSetRecordDTOMeta = InframonitoringtypesDaemonSetRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesDaemonSetRecordDTO {
/**
@@ -7718,8 +7801,7 @@ export type InframonitoringtypesDeploymentRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesDeploymentRecordDTOMeta =
InframonitoringtypesDeploymentRecordDTOMetaAnyOf | null;
export type InframonitoringtypesDeploymentRecordDTOMeta = InframonitoringtypesDeploymentRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesDeploymentRecordDTO {
/**
@@ -7808,8 +7890,7 @@ export type InframonitoringtypesHostRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesHostRecordDTOMeta =
InframonitoringtypesHostRecordDTOMetaAnyOf | null;
export type InframonitoringtypesHostRecordDTOMeta = InframonitoringtypesHostRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesHostRecordDTO {
/**
@@ -7891,8 +7972,7 @@ export type InframonitoringtypesJobRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesJobRecordDTOMeta =
InframonitoringtypesJobRecordDTOMetaAnyOf | null;
export type InframonitoringtypesJobRecordDTOMeta = InframonitoringtypesJobRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesJobRecordDTO {
/**
@@ -8010,8 +8090,7 @@ export type InframonitoringtypesNamespaceRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesNamespaceRecordDTOMeta =
InframonitoringtypesNamespaceRecordDTOMetaAnyOf | null;
export type InframonitoringtypesNamespaceRecordDTOMeta = InframonitoringtypesNamespaceRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesNamespaceRecordDTO {
/**
@@ -8078,8 +8157,7 @@ export type InframonitoringtypesNodeRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesNodeRecordDTOMeta =
InframonitoringtypesNodeRecordDTOMetaAnyOf | null;
export type InframonitoringtypesNodeRecordDTOMeta = InframonitoringtypesNodeRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesNodeRecordDTO {
condition: InframonitoringtypesNodeConditionDTO;
@@ -8150,8 +8228,7 @@ export type InframonitoringtypesPodRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesPodRecordDTOMeta =
InframonitoringtypesPodRecordDTOMetaAnyOf | null;
export type InframonitoringtypesPodRecordDTOMeta = InframonitoringtypesPodRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesPodRecordDTO {
/**
@@ -8538,8 +8615,7 @@ export type InframonitoringtypesStatefulSetRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesStatefulSetRecordDTOMeta =
InframonitoringtypesStatefulSetRecordDTOMetaAnyOf | null;
export type InframonitoringtypesStatefulSetRecordDTOMeta = InframonitoringtypesStatefulSetRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesStatefulSetRecordDTO {
/**
@@ -8615,8 +8691,7 @@ export type InframonitoringtypesVolumeRecordDTOMetaAnyOf = {
/**
* @nullable
*/
export type InframonitoringtypesVolumeRecordDTOMeta =
InframonitoringtypesVolumeRecordDTOMetaAnyOf | null;
export type InframonitoringtypesVolumeRecordDTOMeta = InframonitoringtypesVolumeRecordDTOMetaAnyOf | null;
export interface InframonitoringtypesVolumeRecordDTO {
/**
@@ -9734,8 +9809,7 @@ export type PrometheusMatrixSeriesSchemaDTOMetricAnyOf = {
/**
* @nullable
*/
export type PrometheusMatrixSeriesSchemaDTOMetric =
PrometheusMatrixSeriesSchemaDTOMetricAnyOf | null;
export type PrometheusMatrixSeriesSchemaDTOMetric = PrometheusMatrixSeriesSchemaDTOMetricAnyOf | null;
export interface PrometheusMatrixSeriesSchemaDTO {
/**
@@ -9767,8 +9841,7 @@ export type PrometheusVectorSampleSchemaDTOMetricAnyOf = {
/**
* @nullable
*/
export type PrometheusVectorSampleSchemaDTOMetric =
PrometheusVectorSampleSchemaDTOMetricAnyOf | null;
export type PrometheusVectorSampleSchemaDTOMetric = PrometheusVectorSampleSchemaDTOMetricAnyOf | null;
export interface PrometheusVectorSampleSchemaDTO {
/**
@@ -9878,6 +9951,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
@@ -10156,8 +10251,7 @@ export type Querybuildertypesv5RawRowDTODataAnyOf = { [key: string]: unknown };
/**
* @nullable
*/
export type Querybuildertypesv5RawRowDTOData =
Querybuildertypesv5RawRowDTODataAnyOf | null;
export type Querybuildertypesv5RawRowDTOData = Querybuildertypesv5RawRowDTODataAnyOf | null;
export interface Querybuildertypesv5RawRowDTO {
/**
@@ -10228,8 +10322,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.
@@ -11538,8 +11631,7 @@ export type SpantypesSpanMapperTestSpanDTOAttributesAnyOf = {
/**
* @nullable
*/
export type SpantypesSpanMapperTestSpanDTOAttributes =
SpantypesSpanMapperTestSpanDTOAttributesAnyOf | null;
export type SpantypesSpanMapperTestSpanDTOAttributes = SpantypesSpanMapperTestSpanDTOAttributesAnyOf | null;
export type SpantypesSpanMapperTestSpanDTOResourceAnyOf = {
[key: string]: unknown;
@@ -11548,8 +11640,7 @@ export type SpantypesSpanMapperTestSpanDTOResourceAnyOf = {
/**
* @nullable
*/
export type SpantypesSpanMapperTestSpanDTOResource =
SpantypesSpanMapperTestSpanDTOResourceAnyOf | null;
export type SpantypesSpanMapperTestSpanDTOResource = SpantypesSpanMapperTestSpanDTOResourceAnyOf | null;
export interface SpantypesSpanMapperTestSpanDTO {
/**
@@ -11661,8 +11752,7 @@ export type SpantypesSpanAggregationResultDTOValueAnyOf = {
/**
* @nullable
*/
export type SpantypesSpanAggregationResultDTOValue =
SpantypesSpanAggregationResultDTOValueAnyOf | null;
export type SpantypesSpanAggregationResultDTOValue = SpantypesSpanAggregationResultDTOValueAnyOf | null;
export interface SpantypesSpanAggregationResultDTO {
aggregation: SpantypesSpanAggregationTypeDTO;
@@ -11864,16 +11954,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 {
/**
@@ -12313,8 +12401,7 @@ export type TelemetrytypesGettableFieldKeysDTOKeysAnyOf = {
/**
* @nullable
*/
export type TelemetrytypesGettableFieldKeysDTOKeys =
TelemetrytypesGettableFieldKeysDTOKeysAnyOf | null;
export type TelemetrytypesGettableFieldKeysDTOKeys = TelemetrytypesGettableFieldKeysDTOKeysAnyOf | null;
export interface TelemetrytypesGettableFieldKeysDTO {
/**
@@ -13250,6 +13337,14 @@ export type ListPromotedPaths200 = {
status: string;
};
export type IndexMaterializedPaths200 = {
data: PromotetypesIndexMaterializedPathsResultDTO;
/**
* @type string
*/
status: string;
};
export type ListRoles200 = {
/**
* @type array
@@ -14582,6 +14677,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

@@ -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

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

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

@@ -1,7 +1,5 @@
import { DrawerProps } from 'antd';
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
import { ActionItemProps } from 'container/LogDetailedView/LogDetailedView.types';
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
@@ -20,9 +18,7 @@ export type LogDetailProps = {
handleOpenInExplorer?: MouseEventHandler;
getContainer?: DrawerProps['getContainer'];
onApplyLogFilter?: (expression: string) => void;
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
Partial<Pick<ActionItemProps, 'onClickActionItem'>> &
Pick<DrawerProps, 'onClose'>;
} & Pick<DrawerProps, 'onClose'>;
export type LogDetailInnerProps = LogDetailProps & {
log: NonNullable<LogDetailProps['log']>;

View File

@@ -46,8 +46,6 @@ function renderDrawer(props: Partial<LogDetailProps> = {}): void {
<LogDetail
log={mockLog}
selectedTab={VIEW_TYPES.OVERVIEW}
onAddToQuery={jest.fn()}
onClickActionItem={jest.fn()}
onClose={jest.fn()}
{...props}
/>,

View File

@@ -175,21 +175,19 @@ function OptionsMenu({
<div className="title">FORMAT</div>
<div className="menu-items">
{items.map(
(item: any): JSX.Element => (
<div
className="item"
key={item.label}
onClick={(): void => handleMenuItemClick(item.key)}
>
<div className={cx('item-label')}>
{item.label}
{items.map((item: any): JSX.Element => (
<div
className="item"
key={item.label}
onClick={(): void => handleMenuItemClick(item.key)}
>
<div className={cx('item-label')}>
{item.label}
{selectedItem === item.key && <Check size={12} />}
</div>
{selectedItem === item.key && <Check size={12} />}
</div>
),
)}
</div>
))}
</div>
</div>

View File

@@ -11,13 +11,8 @@
border-top: 1px solid var(--l1-border);
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
sans-serif;
Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', sans-serif;
border-right: none;
border-left: none;

View File

@@ -12,13 +12,8 @@ $max-recents-shown: 5;
flex-direction: column;
gap: 8px;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
sans-serif;
Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', sans-serif;
.query-where-clause-editor-container {
position: relative;
@@ -138,8 +133,7 @@ $max-recents-shown: 5;
// least N recents visible.
@for $i from 1 through $max-recents-shown {
&:has(> li:nth-of-type(#{$i}) .cm-completionIcon-recent) {
max-height: $dropdown-base-height +
$recents-header-height +
max-height: $dropdown-base-height + $recents-header-height +
($i * $recent-row-height) !important;
}
}

View File

@@ -1,7 +1,7 @@
import { Button } from '@signozhq/ui/button';
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
import { useSavedViewEnabled } from 'hooks/useSavedViewEnabled';
import { render, screen, userEvent } from 'tests/test-utils';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import { QuickFiltersSource } from '../../types';
import QuickFiltersLayout from '../QuickFiltersLayout';
@@ -197,6 +197,11 @@ describe('QuickFiltersLayout', () => {
expect(screen.queryByTestId('saved-views-panel')).not.toBeInTheDocument();
expect(drawer()).not.toHaveClass('isOpen');
await waitFor(() =>
expect(screen.getByTestId('saved-views-open')).not.toHaveAttribute(
'aria-busy',
),
);
await user.click(screen.getByTestId('saved-views-open'));
expect(screen.getByTestId('saved-views-panel')).toBeInTheDocument();
@@ -227,6 +232,11 @@ describe('QuickFiltersLayout', () => {
'data-source',
SavedviewtypesSourceDTO.traces,
);
await waitFor(() =>
expect(screen.getByTestId('saved-views-open')).not.toHaveAttribute(
'aria-busy',
),
);
await userEvent.setup().click(screen.getByTestId('saved-views-open'));
expect(screen.getByTestId('saved-views-panel')).toHaveAttribute(
'data-source',

View File

@@ -13,26 +13,22 @@ function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
return (
<div className="label-column">
{newLabels.map(
(label: string): JSX.Element => (
<BadgeWithTooltip key={label} label={label} value={value} />
),
)}
{newLabels.map((label: string): JSX.Element => (
<BadgeWithTooltip key={label} label={label} value={value} />
))}
{remainingLabels.length > 0 && (
<Popover
placement="bottomRight"
showArrow={false}
content={
<div>
{labels.map(
(label: string): JSX.Element => (
<div key={label}>
<Badge textTransform="none" variant="solid" color="secondary">
{getLabelAndValueContent(label, value && value[label])}
</Badge>
</div>
),
)}
{labels.map((label: string): JSX.Element => (
<div key={label}>
<Badge textTransform="none" variant="solid" color="secondary">
{getLabelAndValueContent(label, value && value[label])}
</Badge>
</div>
))}
</div>
}
trigger="hover"

View File

@@ -50,14 +50,11 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
const onCreateRef = useRef(onCreate);
const onDeleteRef = useRef(onDelete);
useImperativeHandle(
ref,
(): ToggleGraphProps => ({
toggleGraph(graphIndex: number, isVisible: boolean): void {
chartRef.current?.setSeries(graphIndex, { show: isVisible });
},
}),
);
useImperativeHandle(ref, (): ToggleGraphProps => ({
toggleGraph(graphIndex: number, isVisible: boolean): void {
chartRef.current?.setSeries(graphIndex, { show: isVisible });
},
}));
useEffect(() => {
onCreateRef.current = onCreate;

View File

@@ -511,7 +511,7 @@ export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
};
export enum OperatorConfigKeys {
'EXCEPTIONS' = 'EXCEPTIONS',
EXCEPTIONS = 'EXCEPTIONS',
}
export const OPERATORS_CONFIG = {

View File

@@ -13,20 +13,19 @@ jest.mock(
'../Explorer/Domains/DomainDetails/components/EndPointsDropDown',
() => ({
__esModule: true,
default: jest.fn().mockImplementation(
({ setSelectedEndPointName }): JSX.Element => (
default: jest
.fn()
.mockImplementation(({ setSelectedEndPointName }): JSX.Element => (
<div data-testid="endpoints-dropdown-mock">
<select
data-testid="endpoints-select"
onChange={(e): void => setSelectedEndPointName(e.target.value)}
role="combobox"
>
<option value="/api/test">/api/test</option>
<option value="/api/new-endpoint">/api/new-endpoint</option>
</select>
</div>
),
),
)),
}),
);

View File

@@ -1,12 +1,8 @@
import { useQuery } from 'react-query';
import { Button, Tooltip } from 'antd';
import getAllChannels from 'api/channels/getAll';
import classNames from 'classnames';
import { ChartLine } from '@signozhq/icons';
import { SuccessResponseV2 } from 'types/api';
import { useChannelOptions } from 'hooks/notificationChannels/useChannelOptions';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import { useCreateAlertState } from '../context';
import AdvancedOptions from '../EvaluationSettings/AdvancedOptions';
@@ -21,14 +17,11 @@ function AlertCondition(): JSX.Element {
const { alertType, setAlertType } = useCreateAlertState();
const {
data,
data: channels,
isLoading: isLoadingChannels,
isError: isErrorChannels,
refetch: refreshChannels,
} = useQuery<SuccessResponseV2<Channels[]>, APIError>(['getChannels'], {
queryFn: () => getAllChannels(),
});
const channels = data?.data || [];
} = useChannelOptions();
const showMultipleTabs =
alertType === AlertTypes.ANOMALY_BASED_ALERT ||

View File

@@ -2,12 +2,12 @@ import { QueryClient, QueryClientProvider } from 'react-query';
import { MemoryRouter } from 'react-router-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { Channels } from 'types/api/channels/getAll';
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
import { CreateAlertProvider } from '../../context';
import AlertThreshold from '../AlertThreshold';
const mockChannels: Channels[] = [];
const mockChannels: ChannelOption[] = [];
const mockRefreshChannels = jest.fn();
const mockIsLoadingChannels = false;
const mockIsErrorChannels = false;
@@ -85,16 +85,17 @@ jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
}));
// Mock getAllChannels API
jest.mock('api/channels/getAll', () => ({
jest.mock('hooks/notificationChannels/useChannelOptions', () => ({
__esModule: true,
default: jest.fn(() =>
Promise.resolve({
data: [
{ id: '1', name: 'Email Channel' },
{ id: '2', name: 'Slack Channel' },
] as Channels[],
}),
),
useChannelOptions: jest.fn(() => ({
data: [
{ id: '1', name: 'Email Channel' },
{ id: '2', name: 'Slack Channel' },
] as ChannelOption[],
isLoading: false,
isError: false,
refetch: jest.fn(),
})),
}));
// Mock alert format categories

View File

@@ -3,7 +3,7 @@ import type { DefaultOptionType } from 'antd/es/select';
import { createMockAlertContextState } from 'container/CreateAlertV2/EvaluationSettings/__tests__/testUtils';
import { getAppContextMockState } from 'container/RoutingPolicies/__tests__/testUtils';
import * as appHooks from 'providers/App/App';
import { Channels } from 'types/api/channels/getAll';
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
import * as context from '../../context';
import ThresholdItem from '../ThresholdItem';
@@ -57,7 +57,7 @@ const mockThreshold = {
color: '#ff0000',
};
const mockChannels: Channels[] = [
const mockChannels: ChannelOption[] = [
{
id: TEST_CONSTANTS.CHANNEL_1,
name: TEST_CONSTANTS.EMAIL_CHANNEL_NAME,

View File

@@ -1,5 +1,5 @@
import type { DefaultOptionType } from 'antd/es/select';
import { Channels } from 'types/api/channels/getAll';
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
import {
NotificationSettingsAction,
@@ -21,7 +21,7 @@ export interface ThresholdItemProps {
updateThreshold: UpdateThreshold;
removeThreshold: (thresholdId: string) => void;
showRemoveButton: boolean;
channels: Channels[];
channels: ChannelOption[];
isLoadingChannels: boolean;
units: DefaultOptionType[];
isErrorChannels: boolean;
@@ -29,7 +29,7 @@ export interface ThresholdItemProps {
}
export interface AnomalyAndThresholdProps {
channels: Channels[];
channels: ChannelOption[];
isLoadingChannels: boolean;
isErrorChannels: boolean;
refreshChannels: () => void;

View File

@@ -16,19 +16,19 @@ export interface IAdvancedOptionItemProps {
}
export enum RollingWindowTimeframes {
'LAST_5_MINUTES' = '5m0s',
'LAST_10_MINUTES' = '10m0s',
'LAST_15_MINUTES' = '15m0s',
'LAST_30_MINUTES' = '30m0s',
'LAST_1_HOUR' = '1h0m0s',
'LAST_2_HOURS' = '2h0m0s',
'LAST_4_HOURS' = '4h0m0s',
LAST_5_MINUTES = '5m0s',
LAST_10_MINUTES = '10m0s',
LAST_15_MINUTES = '15m0s',
LAST_30_MINUTES = '30m0s',
LAST_1_HOUR = '1h0m0s',
LAST_2_HOURS = '2h0m0s',
LAST_4_HOURS = '4h0m0s',
}
export enum CumulativeWindowTimeframes {
'CURRENT_HOUR' = 'currentHour',
'CURRENT_DAY' = 'currentDay',
'CURRENT_MONTH' = 'currentMonth',
CURRENT_HOUR = 'currentHour',
CURRENT_DAY = 'currentDay',
CURRENT_MONTH = 'currentMonth',
}
export interface IEvaluationWindowPopoverProps {

View File

@@ -1,19 +1,16 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { Plus } from '@signozhq/icons';
import { Button, Flex, Form, Select, Tooltip } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import getAll from 'api/channels/getAll';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import ROUTES from 'constants/routes';
import { useChannelOptions } from 'hooks/notificationChannels/useChannelOptions';
import useComponentPermission from 'hooks/useComponentPermission';
import { useAppContext } from 'providers/App/App';
import { SuccessResponseV2 } from 'types/api';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef, Labels } from 'types/api/alerts/def';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
import { openInNewTab } from 'utils/navigation';
@@ -47,12 +44,13 @@ function BasicInfo({
}: BasicInfoProps): JSX.Element {
const { t } = useTranslation('alerts');
const { isLoading, data, error, isError, refetch } = useQuery<
SuccessResponseV2<Channels[]>,
APIError
>(['getChannels'], {
queryFn: () => getAll(),
});
const {
isLoading,
data: channels,
error,
isError,
refetch,
} = useChannelOptions();
const { user } = useAppContext();
const [addNewChannelPermission] = useComponentPermission(
@@ -81,7 +79,7 @@ function BasicInfo({
});
};
const noChannels = data?.data?.length === 0;
const noChannels = channels.length === 0;
const handleCreateNewChannels = useCallback(() => {
logEvent('Alert: Create notification channel button clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
@@ -96,7 +94,7 @@ function BasicInfo({
if (!isLoading && isNewRule && !hasLoggedEvent.current) {
logEvent('Alert: New alert creation page visited', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
numberOfChannels: data?.data?.length,
numberOfChannels: channels.length,
});
hasLoggedEvent.current = true;
}
@@ -230,7 +228,7 @@ function BasicInfo({
disabled={shouldBroadCastToAllChannels}
currentValue={alertDef.preferredChannels}
handleCreateNewChannels={handleCreateNewChannels}
channels={data?.data || []}
channels={channels}
isLoading={isLoading}
hasError={isError}
error={error as APIError}

View File

@@ -2,10 +2,10 @@ import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { Plus } from '@signozhq/icons';
import { Select, Spin } from 'antd';
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import { useAppContext } from 'providers/App/App';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import { StyledCreateChannelOption, StyledSelect } from './styles';
@@ -16,7 +16,7 @@ export interface ChannelSelectProps {
onSelectChannels: (s: string[]) => void;
onDropdownOpen: () => void;
isLoading: boolean;
channels: Channels[];
channels: ChannelOption[];
hasError: boolean;
error: APIError;
handleCreateNewChannels: () => void;

View File

@@ -25,10 +25,8 @@ import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants'
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
import { FontSize } from 'container/OptionsMenu/types';
import RunQueryBtn from 'container/QueryBuilder/components/RunQueryBtn/RunQueryBtn';
import { getOldLogsOperatorFromNew } from 'hooks/logs/useActiveLog';
import useLogDetailHandlers from 'hooks/logs/useLogDetailHandlers';
import useScrollToLog from 'hooks/logs/useScrollToLog';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
import { saveRecentQueryByExpression } from 'lib/recentQueries/saveRecentQuery';
import { ILog } from 'types/api/logs/log';
import { DataSource } from 'types/common/queryBuilder';
@@ -82,29 +80,6 @@ function EntityLogsContent({
const { activeLog, selectedTab, handleSetActiveLog, handleCloseLogDetail } =
useLogDetailHandlers();
// TODO: Move away from using onAddToQuery after old drawer cleanup
const onAddToQuery = useCallback(
(fieldKey: string, fieldValue: string, operator: string): void => {
handleCloseLogDetail();
const partExpression = generateFilterQuery({
fieldKey,
fieldValue,
type: getOldLogsOperatorFromNew(operator),
});
const currentUser = userExpression;
const newUser = currentUser.trim()
? `${currentUser} AND ${partExpression}`
: partExpression;
querySearchOnRun(newUser);
logInfraDrawerFilterCustomizedEvent(category, 'logs', newUser, 'logs');
},
[userExpression, querySearchOnRun, handleCloseLogDetail, category],
);
const onApplyLogFilter = useCallback(
(expression: string): void => {
handleCloseLogDetail();
@@ -342,8 +317,6 @@ function EntityLogsContent({
logs={logs}
onNavigateLog={handleSetActiveLog}
selectedTab={selectedTab}
onAddToQuery={onAddToQuery}
onClickActionItem={onAddToQuery}
onApplyLogFilter={onApplyLogFilter}
onScrollToLog={handleScrollToLog}
handleOpenInExplorer={(e) => handleOpenInExplorer(e, activeLog)}

View File

@@ -66,9 +66,9 @@ jest.mock('../MetricsTable', () => ({
__esModule: true,
MetricsTable: jest
.fn()
.mockImplementation(
(): JSX.Element => <div data-testid="metrics-table">Metrics Table</div>,
),
.mockImplementation((): JSX.Element => (
<div data-testid="metrics-table">Metrics Table</div>
)),
}));
const mockUseQuery = jest.fn();

View File

@@ -111,9 +111,9 @@ const POD_STATUS_CATEGORY_MAP: Record<
};
type ErrorStatusKey = {
[K in keyof InframonitoringtypesPodCountsByStatusDTO]: (typeof POD_STATUS_CATEGORY_MAP)[K] extends 'error'
? K
: never;
[
K in keyof InframonitoringtypesPodCountsByStatusDTO
]: (typeof POD_STATUS_CATEGORY_MAP)[K] extends 'error' ? K : never;
}[keyof InframonitoringtypesPodCountsByStatusDTO];
const ERROR_STATUS_LABELS: Record<ErrorStatusKey, string> = {

View File

@@ -259,8 +259,6 @@ function LiveLogsList({
selectedTab={selectedTab}
log={activeLog}
onClose={handleCloseLogDetail}
onAddToQuery={onAddToQuery}
onClickActionItem={onAddToQuery}
handleChangeSelectedView={handleChangeSelectedView}
logs={formattedLogs}
onNavigateLog={handleSetActiveLog}

View File

@@ -1,5 +1,4 @@
import { MetricsType } from 'container/MetricsApplication/constant';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
export type AnyObject = { [key: string]: any };
@@ -19,15 +18,3 @@ export interface DataType {
field: string;
value: string;
}
export interface ActionItemProps {
fieldKey: string;
fieldValue: string;
onClickActionItem: (
fieldKey: string,
fieldValue: string,
operator: string,
dataType?: DataTypes,
fieldType?: string,
) => void;
}

View File

@@ -172,7 +172,7 @@ const normalizeDataType = (
// key is fabricated locally (empty source list), so no click-time getAggregateKeys fetch.
export const getFilterQueryData = (
item: IBuilderQuery,
target: LogFilterTarget,
target: Pick<LogFilterTarget, 'fieldKey' | 'dataType' | 'metricsType'>,
value: unknown,
operator: string,
): IBuilderQuery => {

View File

@@ -74,12 +74,7 @@
}
tr:has(.active-log) {
background-color: rgba(
78,
116,
248,
0.5
) !important; // same as bg-robin-500
background-color: rgba(78, 116, 248, 0.5) !important; // same as bg-robin-500
color: var(--l1-foreground) !important;
}
}

View File

@@ -381,8 +381,6 @@ function LogsExplorerList({
selectedTab={selectedTab}
log={activeLog}
onClose={handleCloseLogDetail}
onAddToQuery={onAddToQuery}
onClickActionItem={onAddToQuery}
handleChangeSelectedView={handleChangeSelectedView}
logs={logs}
onNavigateLog={handleSetActiveLog}

View File

@@ -91,13 +91,8 @@ function LogsPanelComponent({
() => logs.map((log) => FlatLogData(log) as RowData),
[logs],
);
const {
activeLog,
onAddToQuery,
selectedTab,
handleSetActiveLog,
handleCloseLogDetail,
} = useLogDetailHandlers();
const { activeLog, selectedTab, handleSetActiveLog, handleCloseLogDetail } =
useLogDetailHandlers();
const handleRow = useCallback(
(record: RowData): HTMLAttributes<RowData> => ({
@@ -179,8 +174,6 @@ function LogsPanelComponent({
selectedTab={selectedTab}
log={activeLog}
onClose={handleCloseLogDetail}
onAddToQuery={onAddToQuery}
onClickActionItem={onAddToQuery}
isListViewPanel
listViewPanelSelectedFields={widget?.selectedLogFields}
logs={logs}

View File

@@ -14,6 +14,7 @@ import * as useQueryBuilderHooks from 'hooks/queryBuilder/useQueryBuilder';
import * as useHandleExplorerTabChangeHooks from 'hooks/useHandleExplorerTabChange';
import * as appContextHooks from 'providers/App/App';
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
import * as timezoneHooks from 'providers/Timezone';
import store from 'store';
import { LicenseEvent } from 'types/api/licensesV3/getActive';
@@ -147,7 +148,9 @@ function renderExplorer(): void {
<MemoryRouter>
<Provider store={store}>
<ErrorModalProvider>
<Explorer />
<PreferenceContextProvider>
<Explorer />
</PreferenceContextProvider>
</ErrorModalProvider>
</Provider>
</MemoryRouter>

View File

@@ -560,7 +560,7 @@ const onboardingConfigWithLinks = [
key: 'springboot',
label: 'Spring Boot',
imgUrl: springbootUrl,
link: '/docs/instrumentation/opentelemetry-springboot/',
link: '/docs/instrumentation/java/opentelemetry-java/',
question: {
desc: 'What is your Environment?',
type: 'select',
@@ -570,25 +570,25 @@ const onboardingConfigWithLinks = [
key: 'vm',
label: 'VM',
imgUrl: vmUrl,
link: '/docs/instrumentation/opentelemetry-springboot/',
link: '/docs/instrumentation/java/opentelemetry-java/',
},
{
key: 'k8s',
label: 'Kubernetes',
imgUrl: kubernetesUrl,
link: '/docs/instrumentation/opentelemetry-springboot/',
link: '/docs/instrumentation/java/opentelemetry-java/',
},
{
key: 'docker',
label: 'Docker',
imgUrl: dockerUrl,
link: '/docs/instrumentation/opentelemetry-springboot/',
link: '/docs/instrumentation/java/opentelemetry-java/',
},
{
key: 'windows',
label: 'Windows',
imgUrl: windowsUrl,
link: '/docs/instrumentation/opentelemetry-springboot/',
link: '/docs/instrumentation/java/opentelemetry-java/',
},
],
},
@@ -597,7 +597,7 @@ const onboardingConfigWithLinks = [
key: 'tomcat',
label: 'Tomcat',
imgUrl: tomcatUrl,
link: '/docs/instrumentation/opentelemetry-tomcat/',
link: '/docs/instrumentation/java/opentelemetry-tomcat/',
question: {
desc: 'What is your Environment?',
type: 'select',
@@ -607,25 +607,25 @@ const onboardingConfigWithLinks = [
key: 'vm',
label: 'VM',
imgUrl: vmUrl,
link: '/docs/instrumentation/opentelemetry-tomcat/',
link: '/docs/instrumentation/java/opentelemetry-tomcat/',
},
{
key: 'k8s',
label: 'Kubernetes',
imgUrl: kubernetesUrl,
link: '/docs/instrumentation/opentelemetry-tomcat/',
link: '/docs/instrumentation/java/opentelemetry-tomcat/',
},
{
key: 'docker',
label: 'Docker',
imgUrl: dockerUrl,
link: '/docs/instrumentation/opentelemetry-tomcat/',
link: '/docs/instrumentation/java/opentelemetry-tomcat/',
},
{
key: 'windows',
label: 'Windows',
imgUrl: windowsUrl,
link: '/docs/instrumentation/opentelemetry-tomcat/',
link: '/docs/instrumentation/java/opentelemetry-tomcat/',
},
],
},
@@ -634,7 +634,7 @@ const onboardingConfigWithLinks = [
key: 'jboss',
label: 'JBoss/WildFly',
imgUrl: jbossUrl,
link: '/docs/instrumentation/opentelemetry-jboss/',
link: '/docs/instrumentation/java/opentelemetry-jboss/',
question: {
desc: 'What is your Environment?',
type: 'select',
@@ -644,25 +644,25 @@ const onboardingConfigWithLinks = [
key: 'vm',
label: 'VM',
imgUrl: vmUrl,
link: '/docs/instrumentation/opentelemetry-jboss/',
link: '/docs/instrumentation/java/opentelemetry-jboss/',
},
{
key: 'k8s',
label: 'Kubernetes',
imgUrl: kubernetesUrl,
link: '/docs/instrumentation/opentelemetry-jboss/',
link: '/docs/instrumentation/java/opentelemetry-jboss/',
},
{
key: 'docker',
label: 'Docker',
imgUrl: dockerUrl,
link: '/docs/instrumentation/opentelemetry-jboss/',
link: '/docs/instrumentation/java/opentelemetry-jboss/',
},
{
key: 'windows',
label: 'Windows',
imgUrl: windowsUrl,
link: '/docs/instrumentation/opentelemetry-jboss/',
link: '/docs/instrumentation/java/opentelemetry-jboss/',
},
],
},
@@ -671,7 +671,7 @@ const onboardingConfigWithLinks = [
key: 'opentelemetry-quarkus',
label: 'Quarkus',
imgUrl: quarkusUrl,
link: '/docs/instrumentation/opentelemetry-quarkus/',
link: '/docs/instrumentation/java/opentelemetry-quarkus/',
question: {
desc: 'What is your Environment?',
type: 'select',
@@ -681,25 +681,25 @@ const onboardingConfigWithLinks = [
key: 'vm',
label: 'VM',
imgUrl: vmUrl,
link: '/docs/instrumentation/opentelemetry-quarkus/',
link: '/docs/instrumentation/java/opentelemetry-quarkus/',
},
{
key: 'k8s',
label: 'Kubernetes',
imgUrl: kubernetesUrl,
link: '/docs/instrumentation/opentelemetry-quarkus/',
link: '/docs/instrumentation/java/opentelemetry-quarkus/',
},
{
key: 'docker',
label: 'Docker',
imgUrl: dockerUrl,
link: '/docs/instrumentation/opentelemetry-quarkus/',
link: '/docs/instrumentation/java/opentelemetry-quarkus/',
},
{
key: 'windows',
label: 'Windows',
imgUrl: windowsUrl,
link: '/docs/instrumentation/opentelemetry-quarkus/',
link: '/docs/instrumentation/java/opentelemetry-quarkus/',
},
],
},
@@ -708,7 +708,7 @@ const onboardingConfigWithLinks = [
key: 'Others',
label: 'Others',
imgUrl: javaOthersUrl,
link: '/docs/instrumentation/opentelemetry-java/',
link: '/docs/instrumentation/java/opentelemetry-java/',
question: {
desc: 'What is your Environment?',
type: 'select',
@@ -718,25 +718,25 @@ const onboardingConfigWithLinks = [
key: 'vm',
label: 'VM',
imgUrl: vmUrl,
link: '/docs/instrumentation/opentelemetry-java/',
link: '/docs/instrumentation/java/opentelemetry-java/',
},
{
key: 'k8s',
label: 'Kubernetes',
imgUrl: kubernetesUrl,
link: '/docs/instrumentation/opentelemetry-java/',
link: '/docs/instrumentation/java/opentelemetry-java/',
},
{
key: 'docker',
label: 'Docker',
imgUrl: dockerUrl,
link: '/docs/instrumentation/opentelemetry-java/',
link: '/docs/instrumentation/java/opentelemetry-java/',
},
{
key: 'windows',
label: 'Windows',
imgUrl: windowsUrl,
link: '/docs/instrumentation/opentelemetry-java/',
link: '/docs/instrumentation/java/opentelemetry-java/',
},
],
},
@@ -872,7 +872,7 @@ const onboardingConfigWithLinks = [
key: 'nodejs-nestjs',
label: 'NodeJs/NestJS',
imgUrl: nodejsUrl,
link: '/docs/instrumentation/opentelemetry-javascript/',
link: '/docs/instrumentation/javascript/opentelemetry-nodejs/',
question: {
desc: 'What is your Environment?',
type: 'select',
@@ -882,25 +882,25 @@ const onboardingConfigWithLinks = [
key: 'vm',
label: 'VM',
imgUrl: vmUrl,
link: '/docs/instrumentation/opentelemetry-javascript/',
link: '/docs/instrumentation/javascript/opentelemetry-nodejs/',
},
{
key: 'k8s',
label: 'Kubernetes',
imgUrl: kubernetesUrl,
link: '/docs/instrumentation/opentelemetry-javascript/',
link: '/docs/instrumentation/javascript/opentelemetry-nodejs/',
},
{
key: 'docker',
label: 'Docker',
imgUrl: dockerUrl,
link: '/docs/instrumentation/opentelemetry-javascript/',
link: '/docs/instrumentation/javascript/opentelemetry-nodejs/',
},
{
key: 'windows',
label: 'Windows',
imgUrl: windowsUrl,
link: '/docs/instrumentation/opentelemetry-javascript/',
link: '/docs/instrumentation/javascript/opentelemetry-nodejs/',
},
],
},
@@ -909,7 +909,7 @@ const onboardingConfigWithLinks = [
key: 'nextjs',
label: 'NextJS',
imgUrl: nextjsUrl,
link: '/docs/instrumentation/opentelemetry-nextjs/',
link: '/docs/instrumentation/javascript/opentelemetry-nextjs/',
question: {
desc: 'What is your Environment?',
type: 'select',
@@ -919,25 +919,25 @@ const onboardingConfigWithLinks = [
key: 'vm',
label: 'VM',
imgUrl: vmUrl,
link: '/docs/instrumentation/opentelemetry-nextjs/',
link: '/docs/instrumentation/javascript/opentelemetry-nextjs/',
},
{
key: 'k8s',
label: 'Kubernetes',
imgUrl: kubernetesUrl,
link: '/docs/instrumentation/opentelemetry-nextjs/',
link: '/docs/instrumentation/javascript/opentelemetry-nextjs/',
},
{
key: 'docker',
label: 'Docker',
imgUrl: dockerUrl,
link: '/docs/instrumentation/opentelemetry-nextjs/',
link: '/docs/instrumentation/javascript/opentelemetry-nextjs/',
},
{
key: 'windows',
label: 'Windows',
imgUrl: windowsUrl,
link: '/docs/instrumentation/opentelemetry-nextjs/',
link: '/docs/instrumentation/javascript/opentelemetry-nextjs/',
},
],
},
@@ -946,7 +946,7 @@ const onboardingConfigWithLinks = [
key: 'nuxtjs',
label: 'NuxtJS',
imgUrl: nuxtjsUrl,
link: '/docs/instrumentation/opentelemetry-nuxtjs/',
link: '/docs/instrumentation/javascript/opentelemetry-nuxtjs/',
question: {
desc: 'What is your Environment?',
type: 'select',
@@ -956,25 +956,25 @@ const onboardingConfigWithLinks = [
key: 'vm',
label: 'VM',
imgUrl: vmUrl,
link: '/docs/instrumentation/opentelemetry-nuxtjs/',
link: '/docs/instrumentation/javascript/opentelemetry-nuxtjs/',
},
{
key: 'k8s',
label: 'Kubernetes',
imgUrl: kubernetesUrl,
link: '/docs/instrumentation/opentelemetry-nuxtjs/',
link: '/docs/instrumentation/javascript/opentelemetry-nuxtjs/',
},
{
key: 'docker',
label: 'Docker',
imgUrl: dockerUrl,
link: '/docs/instrumentation/opentelemetry-nuxtjs/',
link: '/docs/instrumentation/javascript/opentelemetry-nuxtjs/',
},
{
key: 'windows',
label: 'Windows',
imgUrl: windowsUrl,
link: '/docs/instrumentation/opentelemetry-nuxtjs/',
link: '/docs/instrumentation/javascript/opentelemetry-nuxtjs/',
},
],
},
@@ -983,7 +983,7 @@ const onboardingConfigWithLinks = [
key: 'react-native',
label: 'React Native',
imgUrl: reactjsUrl,
link: '/docs/instrumentation/opentelemetry-react-native/',
link: '/docs/instrumentation/javascript/opentelemetry-react-native/',
},
],
},
@@ -1298,7 +1298,7 @@ const onboardingConfigWithLinks = [
'resque',
],
id: 'ruby',
link: '/docs/instrumentation/opentelemetry-ruby-on-rails/',
link: '/docs/instrumentation/opentelemetry-ruby/',
question: {
desc: 'What is your Environment?',
type: 'select',
@@ -1308,25 +1308,25 @@ const onboardingConfigWithLinks = [
key: 'vm',
label: 'VM',
imgUrl: vmUrl,
link: '/docs/instrumentation/opentelemetry-ruby-on-rails/',
link: '/docs/instrumentation/opentelemetry-ruby/',
},
{
key: 'k8s',
label: 'Kubernetes',
imgUrl: kubernetesUrl,
link: '/docs/instrumentation/opentelemetry-ruby-on-rails/',
link: '/docs/instrumentation/opentelemetry-ruby/',
},
{
key: 'docker',
label: 'Docker',
imgUrl: dockerUrl,
link: '/docs/instrumentation/opentelemetry-ruby-on-rails/',
link: '/docs/instrumentation/opentelemetry-ruby/',
},
{
key: 'windows',
label: 'Windows',
imgUrl: windowsUrl,
link: '/docs/instrumentation/opentelemetry-ruby-on-rails/',
link: '/docs/instrumentation/opentelemetry-ruby/',
},
],
},
@@ -2292,7 +2292,7 @@ const onboardingConfigWithLinks = [
'python structured logging',
'python-logs-auto-instrumentation',
],
link: '/docs/userguide/python-logs-auto-instrumentation/',
link: '/docs/logs-management/send-logs/python-logs/',
},
{
dataSource: 'otel-java-sdk',
@@ -2323,7 +2323,7 @@ const onboardingConfigWithLinks = [
'Logback',
'log4j',
],
link: '/docs/userguide/collecting_application_logs_otel_sdk_java/',
link: '/docs/logs-management/send-logs/java-logs/',
},
{
dataSource: 'nodejs-logs',
@@ -2463,7 +2463,7 @@ const onboardingConfigWithLinks = [
'linux',
],
imgUrl: hostmetricsUrl,
link: '/docs/userguide/hostmetrics/',
link: '/docs/infrastructure-monitoring/user-guides/hostmetrics/',
},
{
dataSource: 'k8s-metrics',
@@ -2495,7 +2495,7 @@ const onboardingConfigWithLinks = [
'telemetry',
],
imgUrl: kubernetesUrl,
link: '/docs/userguide/k8s-metrics/',
link: '/docs/infrastructure-monitoring/user-guides/k8s-metrics/',
},
{
dataSource: 'docker-container-metrics',
@@ -2583,7 +2583,7 @@ const onboardingConfigWithLinks = [
key: 'aws-ec2-metrics-manual',
label: 'Manual Setup',
imgUrl: ec2Url,
link: '/docs/tutorial/opentelemetry-binary-usage-in-virtual-machine/',
link: '/docs/opentelemetry-collection-agents/vm/install/',
},
],
},
@@ -2772,7 +2772,7 @@ const onboardingConfigWithLinks = [
'vpc-logs',
],
imgUrl: vpcUrl,
link: '/docs/aws-monitoring/vpc-logs/',
link: '/docs/aws-monitoring/vpc/',
},
{
dataSource: 'aws-rds',
@@ -3837,7 +3837,7 @@ const onboardingConfigWithLinks = [
'traces',
'tracing',
],
link: '/docs/llm/opentelemetry-openai-monitoring/',
link: '/docs/openai-monitoring/',
},
{
dataSource: 'codex-monitoring',
@@ -4779,7 +4779,7 @@ const onboardingConfigWithLinks = [
link: '/docs/instrumentation/mobile-instrumentation/opentelemetry-flutter/',
},
{
dataSource: 'swift',
dataSource: 'ios-swift',
label: 'iOS Swift',
imgUrl: swiftMonitoringUrl,
tags: ['mobile app monitoring'],
@@ -4808,6 +4808,7 @@ const onboardingConfigWithLinks = [
'traces',
'tracing',
],
id: 'ios-swift',
link: '/docs/instrumentation/mobile-instrumentation/opentelemetry-swiftui/',
},
{
@@ -5473,7 +5474,7 @@ const onboardingConfigWithLinks = [
'telemetry',
],
id: 'hasura',
link: '/docs/integrations/opentelemetry-hasura/',
link: '/docs/integrations/outposts/hasura/',
},
{
dataSource: 'opencode-observability',
@@ -5935,7 +5936,7 @@ const onboardingConfigWithLinks = [
'metrics',
'monitoring',
],
link: '/docs/integrations/flyio/',
link: '/docs/integrations/outposts/flyio/',
},
{
dataSource: 'envoy',
@@ -6513,7 +6514,7 @@ const onboardingConfigWithLinks = [
'tracing',
],
id: 'openclaw-observability',
link: '/docs/openclaw-monitoring/',
link: '/docs/openclaw-observability/',
},
{
dataSource: 'claude-agent-monitoring',
@@ -6555,7 +6556,7 @@ const onboardingConfigWithLinks = [
'render monitoring',
],
id: 'render-metrics',
link: '/docs/metrics-management/render-metrics/',
link: '/docs/integrations/outposts/render/',
},
{
dataSource: 'cert-manager',
@@ -6579,7 +6580,7 @@ const onboardingConfigWithLinks = [
'tls',
],
id: 'cert-manager',
link: '/docs/infrastructure-monitoring/cert-manager/',
link: '/docs/infrastructure-monitoring/user-guides/cert-manager/',
},
{
dataSource: 'pgbouncer',

View File

@@ -10,8 +10,7 @@ import { ILog } from 'types/api/logs/log';
import './styles.scss';
function LogsList({ logs }: LogsListProps): JSX.Element {
const { activeLog, onSetActiveLog, onClearActiveLog, onAddToQuery } =
useActiveLog();
const { activeLog, onSetActiveLog, onClearActiveLog } = useActiveLog();
const makeLogDetailsHandler = (log: ILog) => (): void => onSetActiveLog(log);
@@ -45,8 +44,6 @@ function LogsList({ logs }: LogsListProps): JSX.Element {
selectedTab={VIEW_TYPES.OVERVIEW}
log={activeLog}
onClose={onClearActiveLog}
onAddToQuery={onAddToQuery}
onClickActionItem={onAddToQuery}
/>
</div>
);

View File

@@ -15,16 +15,14 @@ function PromQLQueryContainer(): JSX.Element | null {
return (
<>
{currentQuery.promql.map(
(q: IPromQLQuery, idx: number): JSX.Element => (
<PromQLQueryBuilder
key={q.name}
deletable={currentQuery.promql.length > 1}
queryIndex={idx}
queryData={q}
/>
),
)}
{currentQuery.promql.map((q: IPromQLQuery, idx: number): JSX.Element => (
<PromQLQueryBuilder
key={q.name}
deletable={currentQuery.promql.length > 1}
queryIndex={idx}
queryData={q}
/>
))}
<QueryButton
onClick={addQueryHandler}
icon={<Plus size="md" />}

View File

@@ -329,7 +329,7 @@ describe('transformTransactionGroupsToResourcePermissions', () => {
it('returns all resources from RESOURCE_ORDER even with empty transaction groups', () => {
const result = transformTransactionGroupsToResourcePermissions([]);
expect(result).toHaveLength(11);
expect(result).toHaveLength(12);
expect(result.map((r) => r.resourceKind)).toStrictEqual([
'factor-api-key',
'dashboard',
@@ -339,6 +339,7 @@ describe('transformTransactionGroupsToResourcePermissions', () => {
'metrics',
'quick-filter',
'role',
'saved-view',
'serviceaccount',
'subscription',
'traces',
@@ -422,7 +423,7 @@ describe('createEmptyRolePermissions', () => {
it('creates permissions for all resources in RESOURCE_ORDER', () => {
const result = createEmptyRolePermissions();
expect(result).toHaveLength(11);
expect(result).toHaveLength(12);
expect(result.map((r) => r.resourceKind)).toStrictEqual([
'factor-api-key',
'dashboard',
@@ -432,6 +433,7 @@ describe('createEmptyRolePermissions', () => {
'metrics',
'quick-filter',
'role',
'saved-view',
'serviceaccount',
'subscription',
'traces',

View File

@@ -10,6 +10,7 @@ import {
Logs,
Receipt,
Shield,
TowerControl,
} from '@signozhq/icons';
import permissionsType from 'lib/authz/hooks/useAuthZ/permissions.type';
@@ -95,6 +96,14 @@ export const RESOURCE_PANELS: Record<AuthZResource, ResourcePanelConfig> = {
'Type quick filter ID, separate multiple with comma or space',
docsAnchor: 'quick-filter',
},
'saved-view': {
label: 'Saved Views',
description: 'Saved views in the logs, traces, metrics and meter explorers.',
icon: TowerControl,
selectorPlaceholder:
'Type saved view ID, separate multiple with comma or space',
docsAnchor: 'saved-view',
},
logs: {
label: 'Logs',
description: 'Log data collected across the workspace.',

View File

@@ -1,6 +1,6 @@
import { ApiRoutingPolicy } from 'api/routingPolicies/getRoutingPolicies';
import { IAppContext, IUser } from 'providers/App/types';
import { Channels } from 'types/api/channels/getAll';
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
import { RoutingPolicy, UseRoutingPoliciesReturn } from '../types';
@@ -28,21 +28,13 @@ export const MOCK_ROUTING_POLICY_2: RoutingPolicy = {
updatedBy: 'user2@signoz.io',
};
export const MOCK_CHANNEL_1: Channels = {
export const MOCK_CHANNEL_1: ChannelOption = {
name: 'Channel 1',
created_at: '2021-01-01',
data: 'data 1',
id: '1',
type: 'type 1',
updated_at: '2021-01-01',
};
export const MOCK_CHANNEL_2: Channels = {
export const MOCK_CHANNEL_2: ChannelOption = {
name: 'Channel 2',
created_at: '2021-01-02',
data: 'data 2',
id: '2',
type: 'type 2',
updated_at: '2021-01-02',
};
export function getUseRoutingPoliciesMockData(

View File

@@ -77,12 +77,14 @@ jest.mock('hooks/routingPolicies/useDeleteRoutingPolicy', () => ({
isLoading: false,
}),
}));
jest.mock('api/channels/getAll', () => ({
jest.mock('hooks/notificationChannels/useChannelOptions', () => ({
__esModule: true,
default: (): any =>
Promise.resolve({
data: [MOCK_CHANNEL_1, MOCK_CHANNEL_2],
}),
useChannelOptions: (): any => ({
data: [MOCK_CHANNEL_1, MOCK_CHANNEL_2],
isLoading: false,
isError: false,
refetch: jest.fn(),
}),
}));
const ROUTING_POLICY_1_NAME = 'Routing Policy 1';

View File

@@ -1,4 +1,4 @@
import { Channels } from 'types/api/channels/getAll';
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
export interface RoutingPolicy {
id: string;
@@ -62,7 +62,7 @@ export interface RoutingPolicyDetailsProps {
routingPolicy: RoutingPolicy | null;
closeModal: () => void;
mode: PolicyDetailsModalMode;
channels: Channels[];
channels: ChannelOption[];
isErrorChannels: boolean;
isLoadingChannels: boolean;
handlePolicyDetailsModalAction: HandlePolicyDetailsModalAction;
@@ -86,7 +86,7 @@ export interface UseRoutingPoliciesReturn {
isErrorRoutingPolicies: boolean;
refetchRoutingPolicies: () => void;
// Channels
channels: Channels[];
channels: ChannelOption[];
isLoadingChannels: boolean;
isErrorChannels: boolean;
refreshChannels: () => void;

View File

@@ -1,19 +1,17 @@
import { useMemo, useState } from 'react';
import { useQuery, useQueryClient } from 'react-query';
import { useQueryClient } from 'react-query';
import { useHistory } from 'react-router-dom';
import { toast } from '@signozhq/ui/sonner';
import getAllChannels from 'api/channels/getAll';
import { GetRoutingPoliciesResponse } from 'api/routingPolicies/getRoutingPolicies';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useCreateRoutingPolicy } from 'hooks/routingPolicies/useCreateRoutingPolicy';
import { useDeleteRoutingPolicy } from 'hooks/routingPolicies/useDeleteRoutingPolicy';
import { useChannelOptions } from 'hooks/notificationChannels/useChannelOptions';
import { useGetRoutingPolicies } from 'hooks/routingPolicies/useGetRoutingPolicies';
import { useUpdateRoutingPolicy } from 'hooks/routingPolicies/useUpdateRoutingPolicy';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import useUrlQuery from 'hooks/useUrlQuery';
import { SuccessResponseV2 } from 'types/api';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import {
PolicyDetailsModalMode,
@@ -83,14 +81,11 @@ function useRoutingPolicies(): UseRoutingPoliciesReturn {
// Channels list
const {
data,
data: channels,
isLoading: isLoadingChannels,
isError: isErrorChannels,
refetch: refetchChannels,
} = useQuery<SuccessResponseV2<Channels[]>, APIError>(['getChannels'], {
queryFn: () => getAllChannels(),
});
const channels = data?.data || [];
} = useChannelOptions();
const refreshChannels = (): void => {
refetchChannels();

View File

@@ -1,41 +1,50 @@
import { useMemo } from 'react';
import { Check } from '@signozhq/icons';
import { Dropdown, type DropdownItemType } from '@signozhq/ui/dropdown';
import AuthZDropdown from 'lib/authz/components/AuthZDropdown/AuthZDropdown';
import type { AuthZDropdownItemType } from 'lib/authz/components/AuthZDropdown/types';
import {
buildSavedViewUpdatePermission,
SavedViewCreatePermission,
} from 'lib/authz/hooks/useAuthZ/permissions/saved-view.permissions';
import SavedViewsIconButton from './SavedViewsIconButton';
import { SaveChangesMenuProps } from './types';
function SaveChangesMenu({
viewId,
disabled,
onSaveAsNew,
onUpdate,
}: SaveChangesMenuProps): JSX.Element {
const items = useMemo<DropdownItemType[]>(
const items = useMemo<AuthZDropdownItemType[]>(
() => [
{
type: 'item',
value: 'save-as-new',
label: 'Save as new view',
checks: [SavedViewCreatePermission],
onClick: onSaveAsNew,
},
{
type: 'item',
value: 'update',
label: 'Update selected view',
checks: [buildSavedViewUpdatePermission(viewId)],
onClick: onUpdate,
},
],
[onSaveAsNew, onUpdate],
[viewId, onSaveAsNew, onUpdate],
);
return (
<Dropdown
<AuthZDropdown
items={items}
nativeButton
align="end"
side="bottom"
disabled={disabled}
disabledTooltip="Saving the view"
testId="saved-views-save-changes"
>
<SavedViewsIconButton
title="Save changes"
@@ -43,7 +52,7 @@ function SaveChangesMenu({
color="warning"
testId="saved-views-save-changes"
/>
</Dropdown>
</AuthZDropdown>
);
}

View File

@@ -1,7 +1,8 @@
import { useState } from 'react';
import { Plus } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
import { SavedViewCreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/saved-view.permissions';
import SaveViewModal from './SaveViewModal';
import { SavedViewsEmptyStateProps } from './types';
@@ -31,7 +32,8 @@ function SavedViewsEmptyState({
<Typography.Text className={styles.stateText}>
Save the current view to open it later.
</Typography.Text>
<Button
<AuthZButton
checks={[SavedViewCreatePermission]}
variant="outlined"
color="secondary"
size="sm"
@@ -40,7 +42,7 @@ function SavedViewsEmptyState({
testId="saved-views-list-create"
>
Create view
</Button>
</AuthZButton>
{isModalOpen && (
<SaveViewModal
mode="create"

View File

@@ -78,7 +78,7 @@
display: flex;
align-items: center;
flex-shrink: 0;
gap: var(--spacing-1);
gap: var(--spacing-4);
}
.collapseIcon {

View File

@@ -5,9 +5,17 @@ import { Skeleton } from '@signozhq/ui/skeleton';
import { toast } from '@signozhq/ui/sonner';
import { Tooltip } from '@signozhq/ui/tooltip';
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams';
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import {
buildSavedViewReadPermission,
SavedViewCreatePermission,
SavedViewListPermission,
} from 'lib/authz/hooks/useAuthZ/permissions/saved-view.permissions';
import {
MY_VIEW_NAME,
SAVED_VIEW_FORBIDDEN_NAME,
SAVED_VIEW_LOAD_FAILED_NAME,
SAVED_VIEW_TOAST_POSITION,
} from './constants';
@@ -31,8 +39,16 @@ function SavedViewsHeader({
onOpenViews?: () => void;
onCollapse?: () => void;
}): JSX.Element {
const { view, isLoading, isError, hasUnsavedChanges } =
useActiveSavedView(source);
const {
view,
isLoading,
isError,
isReadDenied,
errorMessage,
hasUnsavedChanges,
} = useActiveSavedView(source);
const { viewKey } = useGetSavedViewParams();
const readPermission = buildSavedViewReadPermission(viewKey);
const { revertView, clearView, createView, updateView, isSaving } =
useSavedViewActions(source);
@@ -49,11 +65,27 @@ function SavedViewsHeader({
const openSaveAsNew = useCallback((): void => setModalMode('saveAsNew'), []);
const closeModal = useCallback((): void => setModalMode(null), []);
const name = isError
? SAVED_VIEW_LOAD_FAILED_NAME
: (view?.spec.displayName ?? MY_VIEW_NAME);
let name = view?.spec.displayName ?? MY_VIEW_NAME;
if (isReadDenied) {
name = SAVED_VIEW_FORBIDDEN_NAME;
} else if (isError) {
name = SAVED_VIEW_LOAD_FAILED_NAME;
}
const isDirty = !isError && !!view && hasUnsavedChanges;
const nameChip = (
<span
className={cx(styles.name, {
[styles.isDirty]: isDirty,
[styles.isError]: isError,
})}
data-testid="saved-views-name"
>
{isDirty && <span className={styles.dot} />}
<span className={styles.nameText}>{name}</span>
</span>
);
return (
<div
className={cx(styles.header, {
@@ -68,6 +100,7 @@ function SavedViewsHeader({
<SavedViewsIconButton
title="All views"
icon={<List size={14} />}
checks={[SavedViewListPermission]}
onClick={onOpenViews}
testId="saved-views-open"
/>
@@ -79,19 +112,10 @@ function SavedViewsHeader({
className={styles.skeleton}
testId="saved-views-loading"
/>
) : isReadDenied ? (
<AuthZTooltip checks={[readPermission]}>{nameChip}</AuthZTooltip>
) : (
<Tooltip title={name}>
<span
className={cx(styles.name, {
[styles.isDirty]: isDirty,
[styles.isError]: isError,
})}
data-testid="saved-views-name"
>
{isDirty && <span className={styles.dot} />}
<span className={styles.nameText}>{name}</span>
</span>
</Tooltip>
<Tooltip title={errorMessage ?? name}>{nameChip}</Tooltip>
)}
</div>
@@ -102,6 +126,7 @@ function SavedViewsHeader({
icon={<Plus size={14} />}
disabled={isLoading}
disabledTooltip="Loading views"
checks={[SavedViewCreatePermission]}
onClick={(): void => setModalMode('create')}
testId="saved-views-create"
/>
@@ -117,6 +142,7 @@ function SavedViewsHeader({
{view && isDirty && (
<>
<SaveChangesMenu
viewId={view.id}
disabled={isSaving}
onSaveAsNew={openSaveAsNew}
onUpdate={(): void => {

View File

@@ -1,6 +1,7 @@
import { forwardRef } from 'react';
import { Button, type VariantColorType } from '@signozhq/ui/button';
import { Tooltip } from '@signozhq/ui/tooltip';
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
import { SavedViewsIconButtonProps } from './types';
@@ -25,11 +26,33 @@ const SavedViewsIconButton = forwardRef<
color = 'secondary',
disabled,
disabledTooltip,
checks,
onClick,
...triggerProps
},
ref,
): JSX.Element {
if (checks?.length) {
return (
<Tooltip title={title}>
<AuthZButton
checks={checks}
{...VARIANT_COLORS[color]}
size="sm"
icon
aria-label={title}
disabled={disabled}
// AuthZButton skips its checks while a block reason is set.
disabledTooltip={disabled ? disabledTooltip : undefined}
onClick={onClick}
testId={testId}
>
{icon}
</AuthZButton>
</Tooltip>
);
}
return (
<Tooltip title={title}>
<Button

View File

@@ -41,7 +41,7 @@
.row {
display: flex;
align-items: center;
gap: var(--spacing-2);
gap: var(--spacing-4);
height: var(--spacing-16);
padding-right: var(--spacing-4);
border-radius: var(--radius-2);
@@ -71,7 +71,7 @@
.row:hover .rowMenu,
.row:focus-within .rowMenu,
.rowMenu:has([data-state='open']) {
.rowMenu:has([data-popup-open]) {
width: auto;
opacity: 1;
}

View File

@@ -40,8 +40,14 @@ function SavedViewsList({
const saveNewView = useSaveNewView(createView);
const { createdByMe, createdByOthers, isLoading, isError, refetch } =
useSavedViewsList(source, search);
const { copyViewLink, renameView, deleteView, isRenaming, isDeleting } =
useSavedViewRowActions(source);
const {
copyViewLink,
copyViewId,
renameView,
deleteView,
isRenaming,
isDeleting,
} = useSavedViewRowActions(source);
const [pendingAction, setPendingAction] =
useState<PendingSavedViewAction | null>(null);
const listRef = useRef<HTMLDivElement>(null);
@@ -79,10 +85,14 @@ function SavedViewsList({
copyViewLink(view);
return;
}
if (key === 'copyId') {
copyViewId(view);
return;
}
closeHoverCard();
setPendingAction({ key, view });
},
[copyViewLink, closeHoverCard],
[copyViewLink, copyViewId, closeHoverCard],
);
const closePendingAction = useCallback((): void => setPendingAction(null), []);

View File

@@ -1,6 +1,8 @@
import cx from 'classnames';
import { Undo2 } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
import { buildSavedViewReadPermission } from 'lib/authz/hooks/useAuthZ/permissions/saved-view.permissions';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
import SavedViewsRowButton from './SavedViewsRowButton';
import SavedViewsRowMenu from './SavedViewsRowMenu';
@@ -17,17 +19,26 @@ function SavedViewsListRow({
hover,
}: SavedViewsListRowProps): JSX.Element {
const name = view.spec.displayName;
const readPermission = buildSavedViewReadPermission(view.id);
// Same cached check as the button; gates the hover card.
const { isLoading, deniedPermissions } = useAuthZ([readPermission]);
const canRead = !isLoading && deniedPermissions.length === 0;
return (
<div
className={cx(styles.row, { [styles.isActive]: isActive })}
data-testid="saved-views-row"
data-view-id={view.id}
onMouseEnter={(event): void => hover.onRowEnter(view, event.currentTarget)}
onMouseEnter={(event): void => {
if (canRead) {
hover.onRowEnter(view, event.currentTarget);
}
}}
onMouseLeave={hover.onRowLeave}
>
<div className={styles.rowName}>
<Button
<AuthZButton
checks={[readPermission]}
size="md"
variant="ghost"
color="secondary"
@@ -41,7 +52,7 @@ function SavedViewsListRow({
testId="saved-views-row-name"
>
{name}
</Button>
</AuthZButton>
</div>
{isActive && (
<SavedViewsRowButton
@@ -54,6 +65,7 @@ function SavedViewsListRow({
)}
<span className={styles.rowMenu}>
<SavedViewsRowMenu
viewId={view.id}
onAction={(key): void => onAction(view, key)}
onOpen={hover.onMenuOpen}
/>

View File

@@ -4,6 +4,9 @@ import { Input } from '@signozhq/ui/input';
import { Typography } from '@signozhq/ui/typography';
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
import { AuthZGuardContent } from 'lib/authz/components/AuthZGuard/AuthZGuardContent';
import { SavedViewListPermission } from 'lib/authz/hooks/useAuthZ/permissions/saved-view.permissions';
import SavedViewsIconButton from './SavedViewsIconButton';
import SavedViewsList from './SavedViewsList';
@@ -39,16 +42,20 @@ function SavedViewsPanel({
/>
</div>
)}
<div className={styles.search}>
<Input
value={search}
onChange={(event): void => setSearch(event.target.value)}
placeholder="Search..."
aria-label="Search views"
testId="saved-views-search"
/>
</div>
<SavedViewsList source={source} search={search} />
<AuthZGuardContent checks={[SavedViewListPermission]}>
<>
<div className={styles.search}>
<Input
value={search}
onChange={(event): void => setSearch(event.target.value)}
placeholder="Search..."
aria-label="Search views"
testId="saved-views-search"
/>
</div>
<SavedViewsList source={source} search={search} />
</>
</AuthZGuardContent>
</div>
);
}

View File

@@ -1,16 +1,18 @@
import { useMemo } from 'react';
import { Ellipsis } from '@signozhq/icons';
import { Dropdown, type DropdownItemType } from '@signozhq/ui/dropdown';
import AuthZDropdown from 'lib/authz/components/AuthZDropdown/AuthZDropdown';
import type { AuthZDropdownItemType } from 'lib/authz/components/AuthZDropdown/types';
import { SAVED_VIEW_ROW_ACTIONS } from './constants';
import SavedViewsRowButton from './SavedViewsRowButton';
import { SavedViewsRowMenuProps } from './types';
function SavedViewsRowMenu({
viewId,
onAction,
onOpen,
}: SavedViewsRowMenuProps): JSX.Element {
const items = useMemo<DropdownItemType[]>(
const items = useMemo<AuthZDropdownItemType[]>(
() =>
SAVED_VIEW_ROW_ACTIONS.flatMap(
({
@@ -19,7 +21,8 @@ function SavedViewsRowMenu({
icon: Icon,
danger,
hasDividerBefore,
}): DropdownItemType[] => [
buildPermission,
}): AuthZDropdownItemType[] => [
...(hasDividerBefore
? [{ type: 'separator' as const, value: `separator-${key}` }]
: []),
@@ -29,15 +32,16 @@ function SavedViewsRowMenu({
label,
prefix: <Icon size={14} />,
danger,
checks: buildPermission ? [buildPermission(viewId)] : undefined,
onClick: (): void => onAction(key),
},
],
),
[onAction],
[viewId, onAction],
);
return (
<Dropdown
<AuthZDropdown
items={items}
nativeButton
side="right"
@@ -47,6 +51,7 @@ function SavedViewsRowMenu({
onOpen();
}
}}
testId="saved-views-row-menu"
>
<SavedViewsRowButton
icon={<Ellipsis size={14} />}
@@ -54,7 +59,7 @@ function SavedViewsRowMenu({
tooltip="More options"
testId="saved-views-row-menu"
/>
</Dropdown>
</AuthZDropdown>
);
}

View File

@@ -1,4 +1,10 @@
import { act, fireEvent, screen, within } from '@testing-library/react';
import {
act,
fireEvent,
screen,
waitFor,
within,
} from '@testing-library/react';
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
@@ -12,6 +18,7 @@ import {
import SavedViewsPanel from '../SavedViewsPanel';
import { mockSavedViewsApi } from './savedViewsApiMock';
import {
expectUsable,
explorerUrl,
makeView,
queryWith,
@@ -110,6 +117,11 @@ async function renderPanel(
dataSource,
);
await screen.findByTestId('saved-views-list');
await waitFor(() =>
screen
.getAllByTestId('saved-views-row-name')
.forEach((name) => expectUsable(name)),
);
jest.useFakeTimers();
}
@@ -140,6 +152,10 @@ describe('SavedViewHoverCard', () => {
});
afterEach(() => {
// useAuthZ batches checks on a timer; left pending, it blocks later checks.
act(() => {
jest.runOnlyPendingTimers();
});
jest.useRealTimers();
});

View File

@@ -0,0 +1,265 @@
import { fireEvent, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import {
buildSavedViewDeletePermission,
buildSavedViewReadPermission,
buildSavedViewUpdatePermission,
SavedViewCreatePermission,
SavedViewListPermission,
} from 'lib/authz/hooks/useAuthZ/permissions/saved-view.permissions';
import {
AUTHZ_CHECK_URL,
setupAuthzDeny,
} from 'lib/authz/utils/authz-test-utils';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
import { DataSource } from 'types/common/queryBuilder';
import SavedViewsHeader from '../SavedViewsHeader';
import SavedViewsPanel from '../SavedViewsPanel';
import { API, mockSavedViewsApi } from './savedViewsApiMock';
import {
explorerUrl,
makeView,
MENU_CHECK_TIMEOUT,
queryWith,
renderWithExplorerProviders,
viewUrl,
} from './savedViewsTestUtils';
const PATH = ROUTES.TRACES_EXPLORER;
const errors = makeView({ id: 'view-1', displayName: 'Errors' });
const slow = makeView({ id: 'view-2', displayName: 'Slow spans' });
function renderPanel(url = explorerUrl(PATH, {})): void {
renderWithExplorerProviders(
<SavedViewsPanel
source={SavedviewtypesSourceDTO.traces}
onClose={jest.fn()}
/>,
url,
);
}
function renderHeader(
url: string,
): ReturnType<typeof renderWithExplorerProviders> {
return renderWithExplorerProviders(
<SavedViewsHeader
source={SavedviewtypesSourceDTO.traces}
onOpenViews={jest.fn()}
/>,
url,
);
}
const rowNamed = (name: string): HTMLElement =>
screen
.getAllByTestId('saved-views-row')
.find((row) => row.textContent?.includes(name)) as HTMLElement;
async function openRowMenu(name: string): Promise<void> {
await screen.findAllByTestId('saved-views-row');
fireEvent.click(within(rowNamed(name)).getByTestId('saved-views-row-menu'));
}
// Resolves once the row's check is back, so its state is final.
async function settledRow(testId: string): Promise<HTMLElement> {
await waitFor(
() => expect(screen.getByTestId(testId)).not.toHaveAttribute('data-loading'),
MENU_CHECK_TIMEOUT,
);
return screen.getByTestId(testId);
}
describe('SavedViews - AuthZ', () => {
beforeEach(() => {
localStorage.clear();
});
afterEach(() => {
server.resetHandlers();
});
it('shows the denied callout instead of the list without list', async () => {
mockSavedViewsApi([errors]);
server.use(setupAuthzDeny(SavedViewListPermission));
renderPanel();
await expect(
screen.findByText(/not authorized/i),
).resolves.toBeInTheDocument();
expect(screen.queryByTestId('saved-views-list')).toBeNull();
expect(screen.queryByTestId('saved-views-search')).toBeNull();
});
it('disables only the views it cannot read', async () => {
mockSavedViewsApi([errors, slow]);
server.use(setupAuthzDeny(buildSavedViewReadPermission(errors.id)));
renderPanel();
await screen.findAllByTestId('saved-views-row');
const nameOf = (view: string): HTMLElement =>
within(rowNamed(view)).getByTestId('saved-views-row-name');
await waitFor(() =>
expect(nameOf('Errors')).toHaveAttribute('aria-disabled', 'true'),
);
await waitFor(() =>
expect(nameOf('Slow spans')).not.toHaveAttribute('aria-busy'),
);
expect(nameOf('Slow spans')).not.toHaveAttribute('aria-disabled', 'true');
});
it('keeps the row menu on a view it cannot read', async () => {
mockSavedViewsApi([errors]);
server.use(setupAuthzDeny(buildSavedViewReadPermission(errors.id)));
renderPanel();
await openRowMenu('Errors');
await expect(
settledRow('saved-views-row-menu-item-delete'),
).resolves.not.toHaveAttribute('data-disabled');
await expect(
settledRow('saved-views-row-menu-item-editDetails'),
).resolves.not.toHaveAttribute('data-disabled');
await expect(
settledRow('saved-views-row-menu-item-copyLink'),
).resolves.not.toHaveAttribute('data-disabled');
});
it('disables creating without create', async () => {
mockSavedViewsApi([]);
server.use(setupAuthzDeny(SavedViewCreatePermission));
renderPanel();
renderHeader(explorerUrl(PATH, {}));
await waitFor(() =>
expect(screen.getByTestId('saved-views-list-create')).toHaveAttribute(
'aria-disabled',
'true',
),
);
await waitFor(() =>
expect(screen.getByTestId('saved-views-create')).toHaveAttribute(
'aria-disabled',
'true',
),
);
});
it('keeps create disabled while the check is pending', async () => {
mockSavedViewsApi([]);
server.use(
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => res(ctx.delay('infinite'))),
);
renderHeader(explorerUrl(PATH, {}));
const create = screen.getByTestId('saved-views-create');
expect(create).toHaveAttribute('aria-busy', 'true');
await userEvent.click(create);
expect(screen.queryByTestId('save-view-name')).toBeNull();
});
it('disables edit details without update and delete without delete', async () => {
mockSavedViewsApi([errors]);
server.use(
setupAuthzDeny(
buildSavedViewUpdatePermission(errors.id),
buildSavedViewDeletePermission(errors.id),
),
);
renderPanel();
await openRowMenu('Errors');
await expect(
settledRow('saved-views-row-menu-item-editDetails'),
).resolves.toHaveAttribute('data-disabled');
await expect(
settledRow('saved-views-row-menu-item-delete'),
).resolves.toHaveAttribute('data-disabled');
await expect(
settledRow('saved-views-row-menu-item-copyLink'),
).resolves.not.toHaveAttribute('data-disabled');
});
it('disables updating the open view without update, keeping save as new', async () => {
mockSavedViewsApi([errors]);
server.use(setupAuthzDeny(buildSavedViewUpdatePermission(errors.id)));
const { history } = renderHeader(viewUrl(PATH, errors));
await waitFor(() =>
expect(screen.getByTestId('saved-views-name')).toHaveTextContent('Errors'),
);
history.push(
explorerUrl(PATH, {
query: queryWith(DataSource.TRACES, 'has_error = false'),
panelType: PANEL_TYPES.LIST,
viewKey: errors.id,
}),
);
await userEvent.click(await screen.findByTestId('saved-views-save-changes'));
await expect(
settledRow('saved-views-save-changes-item-update'),
).resolves.toHaveAttribute('data-disabled');
await expect(
settledRow('saved-views-save-changes-item-save-as-new'),
).resolves.not.toHaveAttribute('data-disabled');
});
it('says no access when the check denies read, even if the server returns the view', async () => {
mockSavedViewsApi([errors]);
server.use(setupAuthzDeny(buildSavedViewReadPermission(errors.id)));
renderHeader(viewUrl(PATH, errors));
await waitFor(() =>
expect(screen.getByTestId('saved-views-name')).toHaveTextContent(
'No view access',
),
);
await waitFor(() =>
expect(screen.getByTestId('saved-views-name')).toHaveAttribute(
'data-denied-permissions',
),
);
await userEvent.setup().hover(screen.getByTestId('saved-views-name'));
await waitFor(() =>
expect(
screen.queryAllByText(/is not authorized to perform read:saved-view:view-1/)
.length,
).toBeGreaterThan(0),
);
});
it('treats a 403 the check allowed as a failed load, showing the server message', async () => {
const message = 'saved-view:read is missing, ask an admin for it';
mockSavedViewsApi([errors]);
server.use(
rest.get(`${API}/:id`, (_req, res, ctx) =>
res(
ctx.status(403),
ctx.json({ status: 'error', error: { code: 'forbidden', message } }),
),
),
);
renderHeader(viewUrl(PATH, errors));
await waitFor(() =>
expect(screen.getByTestId('saved-views-name')).toHaveTextContent(
'View failed to load',
),
);
await userEvent.setup().hover(screen.getByTestId('saved-views-name'));
await waitFor(() =>
expect(screen.queryAllByText(message).length).toBeGreaterThan(0),
);
});
});

View File

@@ -9,7 +9,9 @@ import { usePreferenceContext } from 'providers/preferences/context/PreferenceCo
import SavedViewsHeader from '../SavedViewsHeader';
import { mockSavedViewsApi } from './savedViewsApiMock';
import {
expectRowUsable,
makeView,
MENU_CHECK_TIMEOUT,
renderWithExplorerProviders,
viewUrl,
} from './savedViewsTestUtils';
@@ -115,9 +117,11 @@ describe('SavedViewsHeader columns', () => {
await userEvent.click(screen.getByTestId('add-column'));
await waitFor(() => expect(isDirty()).toBe(true));
await userEvent.click(screen.getByTestId('saved-views-save-changes'));
await userEvent.click(
await screen.findByRole('menuitem', { name: 'Update selected view' }),
const update = await screen.findByTestId(
'saved-views-save-changes-item-update',
);
await waitFor(() => expectRowUsable(update), MENU_CHECK_TIMEOUT);
await userEvent.click(update);
await waitFor(() => expect(isDirty()).toBe(false));
await userEvent.click(screen.getByTestId('add-column'));

View File

@@ -9,8 +9,11 @@ import { DataSource } from 'types/common/queryBuilder';
import SavedViewsHeader from '../SavedViewsHeader';
import {
expectRowUsable,
expectUsable,
explorerUrl,
makeView,
MENU_CHECK_TIMEOUT,
queryWith,
renderWithExplorerProviders,
urlParam,
@@ -67,9 +70,21 @@ async function openDirtyView(
return rendered;
}
const SAVE_MENU_ITEM_TEST_IDS: Record<string, string> = {
'Save as new view': 'saved-views-save-changes-item-save-as-new',
'Update selected view': 'saved-views-save-changes-item-update',
};
async function chooseFromSaveMenu(item: string): Promise<void> {
await userEvent.click(screen.getByTestId('saved-views-save-changes'));
await userEvent.click(await screen.findByRole('menuitem', { name: item }));
const menuItem = await screen.findByTestId(SAVE_MENU_ITEM_TEST_IDS[item]);
await waitFor(() => expectRowUsable(menuItem), MENU_CHECK_TIMEOUT);
await userEvent.click(menuItem);
}
async function clickCreate(): Promise<void> {
await waitFor(() => expectUsable(screen.getByTestId('saved-views-create')));
await userEvent.click(screen.getByTestId('saved-views-create'));
}
async function submitName(name: string): Promise<void> {
@@ -87,7 +102,7 @@ describe('SavedViewsHeader saving', () => {
const requests = mockSavedViewsApi([], { createdId: 'view-new' });
const { history } = renderHeader(explorerUrl(PATH, {}));
await userEvent.click(screen.getByTestId('saved-views-create'));
await clickCreate();
expect(within(modal()).getByText('Create new view')).toBeInTheDocument();
expect(screen.getByTestId('save-view-submit')).toHaveAttribute(
@@ -130,7 +145,7 @@ describe('SavedViewsHeader saving', () => {
const requests = mockSavedViewsApi([], { createStatus: 500 });
const { history } = renderHeader(explorerUrl(PATH, {}));
await userEvent.click(screen.getByTestId('saved-views-create'));
await clickCreate();
await submitName('Slow checkout');
await waitFor(() => expect(toast.error).toHaveBeenCalled());
@@ -145,7 +160,7 @@ describe('SavedViewsHeader saving', () => {
const requests = mockSavedViewsApi([]);
renderHeader(explorerUrl(PATH, {}));
await userEvent.click(screen.getByTestId('saved-views-create'));
await clickCreate();
await userEvent.type(screen.getByTestId('save-view-name'), 'Draft');
await userEvent.click(screen.getByTestId('save-view-cancel'));

View File

@@ -8,6 +8,7 @@ import ROUTES from 'constants/routes';
import SavedViewsPanel from '../SavedViewsPanel';
import { mockSavedViewsApi } from './savedViewsApiMock';
import {
expectUsable,
explorerUrl,
makeView,
renderWithExplorerProviders,
@@ -84,9 +85,9 @@ describe('SavedViewsPanel list', () => {
const { history } = renderPanel();
await screen.findByTestId('saved-views-list');
await userEvent.click(
within(row('Slow spans')).getByTestId('saved-views-row-name'),
);
const name = within(row('Slow spans')).getByTestId('saved-views-row-name');
await waitFor(() => expectUsable(name));
await userEvent.click(name);
await waitFor(() =>
expect(urlParam(history, QueryParams.viewKey)).toBe(
@@ -161,7 +162,9 @@ describe('SavedViewsPanel list', () => {
const requests = mockSavedViewsApi([], { createdId: 'view-new' });
const { history } = renderPanel();
await userEvent.click(await screen.findByTestId('saved-views-list-create'));
const create = await screen.findByTestId('saved-views-list-create');
await waitFor(() => expectUsable(create));
await userEvent.click(create);
await userEvent.type(screen.getByTestId('save-view-name'), 'First view');
await userEvent.click(screen.getByTestId('save-view-submit'));

View File

@@ -9,8 +9,10 @@ import SavedViewsHeader from '../SavedViewsHeader';
import SavedViewsPanel from '../SavedViewsPanel';
import { mockSavedViewsApi } from './savedViewsApiMock';
import {
expectRowUsable,
explorerUrl,
makeView,
MENU_CHECK_TIMEOUT,
renderWithExplorerProviders,
urlParam,
viewUrl,
@@ -58,13 +60,22 @@ const row = (name: string): HTMLElement =>
.getAllByTestId('saved-views-row')
.find((element) => element.textContent?.includes(name)) as HTMLElement;
const ROW_MENU_ITEM_TEST_IDS: Record<string, string> = {
'Copy link': 'saved-views-row-menu-item-copyLink',
'Copy ID': 'saved-views-row-menu-item-copyId',
'Edit details': 'saved-views-row-menu-item-editDetails',
Delete: 'saved-views-row-menu-item-delete',
};
async function chooseFromRowMenu(
viewName: string,
item: string,
): Promise<void> {
await screen.findByTestId('saved-views-list');
fireEvent.click(within(row(viewName)).getByTestId('saved-views-row-menu'));
fireEvent.click(await screen.findByRole('menuitem', { name: item }));
const menuItem = await screen.findByTestId(ROW_MENU_ITEM_TEST_IDS[item]);
await waitFor(() => expectRowUsable(menuItem), MENU_CHECK_TIMEOUT);
fireEvent.click(menuItem);
}
const chip = (): HTMLElement => screen.getByTestId('saved-views-name');
@@ -76,21 +87,38 @@ describe('SavedViewsPanel row menu', () => {
jest.mocked(toast.success).mockClear();
});
it('offers copy link, edit details and delete', async () => {
it('offers copy link, copy id, edit details and delete', async () => {
mockSavedViewsApi([errors]);
renderPanelWithHeader();
await screen.findByTestId('saved-views-list');
fireEvent.click(within(row('Errors')).getByTestId('saved-views-row-menu'));
const items = await screen.findAllByRole('menuitem');
expect(items.map((item) => item.textContent)).toStrictEqual([
await screen.findByTestId(ROW_MENU_ITEM_TEST_IDS['Copy link']);
const items = Object.values(ROW_MENU_ITEM_TEST_IDS).map(
(testId) => screen.getByTestId(testId).textContent,
);
expect(items).toStrictEqual([
'Copy link',
'Copy ID',
'Edit details',
'Delete',
]);
});
it('copies the view id', async () => {
mockSavedViewsApi([errors, slow]);
renderPanelWithHeader();
await chooseFromRowMenu('Slow spans', 'Copy ID');
expect(mockCopy).toHaveBeenCalledWith(slow.id);
expect(toast.success).toHaveBeenCalledWith(
'View ID copied',
expect.anything(),
);
});
it('copies a link that opens the view with its query and tab', async () => {
mockSavedViewsApi([errors, slow]);
renderPanelWithHeader();

View File

@@ -1,4 +1,5 @@
import { SavedviewtypesSavedViewDTO } from 'api/generated/services/sigNoz.schemas';
import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
@@ -13,6 +14,7 @@ export interface Requests {
}
// Mutates `views` on create, update and delete, so a refetch sees the change.
// Grants every permission; a later server.use overrides it.
export function mockSavedViewsApi(
views: SavedviewtypesSavedViewDTO[],
{
@@ -30,6 +32,7 @@ export function mockSavedViewsApi(
const requests: Requests = { created: [], updated: [], deleted: [] };
let failedLists = 0;
server.use(
setupAuthzAdmin(),
rest.get(API, (req, res, ctx) => {
if (failedLists < listFailures) {
failedLists += 1;

View File

@@ -251,3 +251,20 @@ export function renderWithExplorerProviders(
export function urlParam(history: MemoryHistory, key: string): string | null {
return new URLSearchParams(history.location.search).get(key);
}
// The ui Button marks a pending check with aria-busy and a block with
// aria-disabled, never the disabled attribute.
export function expectUsable(element: HTMLElement): void {
expect(element).not.toHaveAttribute('aria-busy');
expect(element).not.toHaveAttribute('aria-disabled', 'true');
}
// A menu checks its rows on first open; on a loaded CI runner that outlasts
// waitFor's 1s default.
export const MENU_CHECK_TIMEOUT = { timeout: 5000 };
// A menu row waiting on its check is data-loading, not yet data-disabled.
export function expectRowUsable(row: HTMLElement): void {
expect(row).not.toHaveAttribute('data-loading');
expect(row).not.toHaveAttribute('data-disabled');
}

View File

@@ -1,9 +1,13 @@
import { Link, PenLine, Trash2 } from '@signozhq/icons';
import { Copy, Link, PenLine, Trash2 } from '@signozhq/icons';
import {
SavedviewtypesSchemaVersionDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import { QueryParams } from 'constants/query';
import {
buildSavedViewDeletePermission,
buildSavedViewUpdatePermission,
} from 'lib/authz/hooks/useAuthZ/permissions/saved-view.permissions';
import { DataSource } from 'types/common/queryBuilder';
import { SavedViewRowActionConfig, SaveViewModalMode } from './types';
@@ -30,6 +34,8 @@ export const MY_VIEW_NAME = 'My view';
export const SAVED_VIEW_LOAD_FAILED_NAME = 'View failed to load';
export const SAVED_VIEW_FORBIDDEN_NAME = 'No view access';
export const SAVED_VIEWS_LIST_LOADING_ROWS = 4;
export const SAVED_VIEW_HOVER_CARD_OPEN_DELAY_MS = 1000;
@@ -47,13 +53,20 @@ export const SAVE_VIEW_MODAL_TITLE: Record<SaveViewModalMode, string> = {
export const SAVED_VIEW_ROW_ACTIONS: SavedViewRowActionConfig[] = [
{ key: 'copyLink', label: 'Copy link', icon: Link },
{ key: 'editDetails', label: 'Edit details', icon: PenLine },
{ key: 'copyId', label: 'Copy ID', icon: Copy },
{
key: 'editDetails',
label: 'Edit details',
icon: PenLine,
buildPermission: buildSavedViewUpdatePermission,
},
{
key: 'delete',
label: 'Delete',
icon: Trash2,
danger: true,
hasDividerBefore: true,
buildPermission: buildSavedViewDeletePermission,
},
];

View File

@@ -6,7 +6,10 @@ import useOptionsMenu from 'container/OptionsMenu/useOptionsMenu';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams';
import { buildSavedViewReadPermission } from 'lib/authz/hooks/useAuthZ/permissions/saved-view.permissions';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
import { DataSource } from 'types/common/queryBuilder';
import { toAPIError } from 'utils/errorUtils';
import { SAVED_VIEW_OPTIONS_DATA_SOURCE } from '../constants';
import { UseActiveSavedViewResult } from '../types';
@@ -23,11 +26,19 @@ export function useActiveSavedView(
dataSource: optionsDataSource ?? DataSource.LOGS,
});
const { data, isLoading, isError } = useGetSavedView(
{ id: viewKey },
{ query: { enabled: !!viewKey } },
const { isLoading: isCheckingRead, deniedPermissions } = useAuthZ(
[buildSavedViewReadPermission(viewKey)],
{ enabled: !!viewKey },
);
const view = viewKey ? data?.data : undefined;
const isReadDenied = !!viewKey && deniedPermissions.length > 0;
const canFetch = !!viewKey && !isCheckingRead && !isReadDenied;
const { data, isLoading, isError, error } = useGetSavedView(
{ id: viewKey },
{ query: { enabled: canFetch } },
);
const view = canFetch ? data?.data : undefined;
// The query builder stages the url query a render later. Until the ids
// match, keep the last answer for this view; another view starts clean.
@@ -68,10 +79,17 @@ export function useActiveSavedView(
lastResult.current = { viewId: view?.id, hasUnsavedChanges };
}, [view?.id, hasUnsavedChanges]);
const errorMessage =
viewKey && isError && error
? toAPIError(error, '').getErrorMessage() || undefined
: undefined;
return {
view,
isLoading: !!viewKey && isLoading,
isError: !!viewKey && isError,
isLoading: !!viewKey && (isCheckingRead || isLoading),
isError: !!viewKey && (isError || isReadDenied),
isReadDenied,
errorMessage,
hasUnsavedChanges,
};
}

View File

@@ -59,6 +59,14 @@ export function useSavedViewRowActions(
[pathname, copyToClipboard],
);
const copyViewId = useCallback(
(view: SavedviewtypesSavedViewDTO): void => {
copyToClipboard(view.id);
toast.success('View ID copied', { position: SAVED_VIEW_TOAST_POSITION });
},
[copyToClipboard],
);
const renameView = useCallback(
async (
view: SavedviewtypesSavedViewDTO,
@@ -101,5 +109,12 @@ export function useSavedViewRowActions(
[deleteSavedView, source, queryClient],
);
return { copyViewLink, renameView, deleteView, isRenaming, isDeleting };
return {
copyViewLink,
copyViewId,
renameView,
deleteView,
isRenaming,
isDeleting,
};
}

View File

@@ -0,0 +1,329 @@
/**
* AI-owned. Generated and maintained by the `signoz-page-story` skill.
* Do not hand-edit: regenerate instead.
*/
import { rest, type RequestHandler } from 'msw';
import { TooltipProvider } from '@signozhq/ui/tooltip';
import remove from 'api/browser/localstorage/remove';
import {
type AuthtypesTransactionDTO,
type SavedviewtypesSavedViewDTO,
SavedviewtypesSourceDTO,
type SavedviewtypesUpdatableSavedViewDTO,
} from 'api/generated/services/sigNoz.schemas';
import { SIDEBAR_TOOLTIP_DELAY_MS } from 'components/QuickFilters/QuickFiltersLayout/constants';
import { LOCALSTORAGE } from 'constants/localStorage';
import {
AUTHZ_CHECK_URL,
authzMockResponse,
} from 'lib/authz/utils/authz-test-utils';
import { expect, screen, userEvent, waitFor, within } from 'storybook/test';
import { accessFor } from '@/storybook/access/access';
import {
choiceControl,
countControl,
toggleControl,
} from '@/storybook/controls/controls';
import { defineStoryMocks } from '@/storybook/controls/defineStoryMocks';
import type { MockControlValues, StoryMocks } from '@/storybook/controls/types';
import { withCanvas } from '@/storybook/decorators/withCanvas';
import { authzMocks, type AuthzArgs } from '@/storybook/globals/authzMocks';
import { respondWith } from '@/storybook/runtime/responseState';
import {
createdSavedView,
OPEN_VIEW_ERRORS,
OPEN_VIEWS,
type OpenView,
SAVED_VIEW_SOURCES,
SAVED_VIEWS_MAX,
savedViewById,
savedViewResponse,
savedViewRoute,
savedViewsListResponse,
type SavedViewSource,
unreadableViewId,
} from './__story_mockdata__/savedViews';
import SavedViewsHeader from '../SavedViewsHeader';
import SavedViewsPanel from '../SavedViewsPanel';
import type { SavedViewRowActionKey } from '../types';
const API = 'http://localhost/api/v2/saved_views';
const LIST = 'Saved Views · list';
const VIEW = 'Saved Views · open view';
const controls = {
source: choiceControl<SavedViewSource>('Source', {
group: VIEW,
description: 'Which explorer the views belong to.',
options: SAVED_VIEW_SOURCES,
value: SavedviewtypesSourceDTO.traces,
}),
openView: choiceControl<OpenView>('Open view', {
group: VIEW,
description:
'The `viewKey` the explorer url carries. `unsaved-changes` stages a query that differs from the view, which swaps in the ✓ save menu; `forbidden`, `failed` and `not-found` are the 403, 500 and 404 the view fetch answers with; each shows View failed to load.',
options: OPEN_VIEWS,
value: 'none',
}),
unreadableView: toggleControl('Unreadable view', {
group: VIEW,
description:
'Denies `read` on the first view created by others alone, on top of the Access controls: its row greys out, and with Open view on `created-by-others` the header shows No view access.',
value: false,
}),
createdByMe: countControl('Created by me', {
group: LIST,
value: 3,
max: SAVED_VIEWS_MAX,
}),
createdByOthers: countControl('Created by others', {
group: LIST,
description: 'Both at 0 is the empty state, with its Create view button.',
value: 4,
max: SAVED_VIEWS_MAX,
}),
};
type SavedViewsValues = MockControlValues<typeof controls>;
type WrittenView = Pick<SavedviewtypesSavedViewDTO, 'spec' | 'source'>;
const viewDenied = (
transaction: AuthtypesTransactionDTO,
deniedId: string,
): boolean =>
transaction.relation === 'read' &&
transaction.object.resource.kind === 'saved-view' &&
transaction.object.selector === deniedId;
const definition = defineStoryMocks({
controls,
handlers: (
values: SavedViewsValues & AuthzArgs,
response,
): RequestHandler[] => {
const { source, openView, createdByMe, createdByOthers } = values;
// What the panel writes stays in this handler set until a control changes.
const created: SavedviewtypesSavedViewDTO[] = [];
const updated = new Map<string, WrittenView>();
const deleted = new Set<string>();
const current = (
view: SavedviewtypesSavedViewDTO,
): SavedviewtypesSavedViewDTO => ({ ...view, ...updated.get(view.id) });
const findView = (id: string): SavedviewtypesSavedViewDTO | undefined => {
const view = created.find((item) => item.id === id) ?? savedViewById(id);
return view && !deleted.has(id) ? current(view) : undefined;
};
const openError = OPEN_VIEW_ERRORS[openView];
return [
rest.post(API, async (req, res, ctx) => {
const body = (await req.json()) as SavedviewtypesUpdatableSavedViewDTO;
const id = `storybook-${source}-created-${created.length + 1}`;
created.push(createdSavedView(source, id, body.spec));
return res(ctx.status(201), ctx.json({ status: 'success', data: { id } }));
}),
rest.get(
API,
response.json(() => {
const list = savedViewsListResponse(source, createdByMe, createdByOthers);
return {
...list,
data: [...(list.data ?? []), ...created]
.filter((view) => !deleted.has(view.id))
.map(current),
};
}),
),
rest.get(`${API}/:id`, (req, res, ctx) => {
if (openError) {
return res(ctx.status(openError.status), ctx.json(openError.body));
}
const view = findView(String(req.params.id));
if (!view) {
return res(ctx.status(404), ctx.json(OPEN_VIEW_ERRORS['not-found']?.body));
}
return response.json(() => savedViewResponse(view))(req, res, ctx);
}),
rest.put(`${API}/:id`, async (req, res, ctx) => {
const body = (await req.json()) as SavedviewtypesUpdatableSavedViewDTO;
updated.set(String(req.params.id), {
spec: body.spec,
source: body.source,
});
return res(ctx.status(204));
}),
rest.delete(`${API}/:id`, (req, res, ctx) => {
deleted.add(String(req.params.id));
return res(ctx.status(204));
}),
...(values.unreadableView
? [
rest.post(
AUTHZ_CHECK_URL,
respondWith(values.authzState, async (req) => {
const payload = (await req.json()) as AuthtypesTransactionDTO[];
const granted = accessFor(
values.access,
values.permissions,
values.revoked,
);
const deniedId = unreadableViewId(source);
return authzMockResponse(
payload,
payload.map(
(transaction) =>
!viewDenied(transaction, deniedId) && granted.allows(transaction),
),
);
}),
),
]
: []),
];
},
config: ({ source, openView }) => ({
route: savedViewRoute(source, openView),
}),
effect: () => {
remove(LOCALSTORAGE.LAST_USED_SAVED_VIEWS);
},
});
/**
* The per-view read denial answers `authz/check` in place of the global access
* mocks, so it reads their controls as well as its own to answer every other
* check the way they would.
*/
export const savedViewsMocks: StoryMocks<typeof controls> = {
...definition,
read: (args) => ({ ...authzMocks.read(args), ...definition.read(args) }),
};
/** The sidebar slot of an explorer without quick filters. */
export const sidebarCanvas = withCanvas({
width: 280,
height: 640,
padding: 0,
display: 'flex',
flexDirection: 'column',
borderRight: '1px solid var(--l1-border)',
});
/**
* The header above the pinned list. `onOpenViews` adds All views, which the
* header only shows in an explorer whose quick filters hide the list.
*/
export function SavedViewsSidebar({
source,
onOpenViews,
}: {
source: SavedViewSource;
onOpenViews?: () => void;
}): JSX.Element {
return (
<TooltipProvider delay={SIDEBAR_TOOLTIP_DELAY_MS}>
<SavedViewsHeader source={source} onOpenViews={onOpenViews} />
<SavedViewsPanel source={source} />
</TooltipProvider>
);
}
/** The panel fetches the list and checks every row's permissions before it settles. */
export const UNTIL_LOADED = { timeout: 15_000 };
/**
* Re-queried on every attempt: a button whose check settles is a new node, so a
* reference taken while it was pending never changes.
*/
type FindElement = () => HTMLElement;
export const byTestId =
(testId: string): FindElement =>
(): HTMLElement =>
screen.getByTestId(testId);
/** A check still in flight: `aria-busy` on a button, `data-loading` on a menu row. */
const isPending = (element: HTMLElement): boolean =>
element.getAttribute('aria-busy') === 'true' ||
element.hasAttribute('data-loading');
const isDisabled = (element: HTMLElement): boolean =>
element.hasAttribute('disabled') || element.hasAttribute('data-disabled');
export const expectDisabled = (
find: FindElement,
disabled = true,
): Promise<void> =>
waitFor(async () => {
const element = find();
await expect(isPending(element)).toBe(false);
await expect(isDisabled(element)).toBe(disabled);
}, UNTIL_LOADED);
export const expectPending = (find: FindElement): Promise<void> =>
waitFor(() => expect(isPending(find())).toBe(true), UNTIL_LOADED);
export const findRows = (canvasElement: HTMLElement): Promise<HTMLElement[]> =>
within(canvasElement).findAllByTestId(
'saved-views-row',
undefined,
UNTIL_LOADED,
);
export const rowName =
(row: HTMLElement): FindElement =>
(): HTMLElement =>
within(row).getByTestId('saved-views-row-name');
export const openRowMenu = async (row: HTMLElement): Promise<void> => {
await userEvent.click(within(row).getByTestId('saved-views-row-menu'));
await screen.findByRole('menu', undefined, UNTIL_LOADED);
};
export const rowMenuItem = (key: SavedViewRowActionKey): FindElement =>
byTestId(`saved-views-row-menu-item-${key}`);
export const openSaveChangesMenu = async (
canvasElement: HTMLElement,
): Promise<void> => {
const trigger = await waitFor(async () => {
const button = within(canvasElement).getByTestId('saved-views-save-changes');
await expect(button).not.toHaveAttribute('aria-busy');
return button;
}, UNTIL_LOADED);
await userEvent.click(trigger);
await screen.findByRole('menu', undefined, UNTIL_LOADED);
};
export const saveChangesItem = (key: 'save-as-new' | 'update'): FindElement =>
byTestId(`saved-views-save-changes-item-${key}`);

View File

@@ -0,0 +1,80 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { storyMocks } from '@/storybook/controls/defineStoryMocks';
import type { PageStoryArgs } from '@/storybook/runtime/resolveStory';
import SavedViewsPanel from '../SavedViewsPanel';
import {
findRows,
openRowMenu,
openSaveChangesMenu,
SavedViewsSidebar,
savedViewsMocks,
sidebarCanvas,
} from './SavedViews.stories.mocks';
type SavedViewsArgs = PageStoryArgs<typeof savedViewsMocks>;
const componentStory = storyMocks(savedViewsMocks);
/**
* The saved views sidebar of an explorer without quick filters: the header with
* the open view and its actions, above the list of views created by the user
* and by others. Every action is gated on the `saved-view` authz permissions
* (list, create, and read, update and delete per view).
*
* Route: the explorer of the Source control, with the Open view control's
* `viewKey`.
*/
const meta = {
title: 'Components/Saved Views/Overview',
tags: ['authz', 'play'],
component: SavedViewsPanel,
decorators: [sidebarCanvas],
render: ({ source }): JSX.Element => <SavedViewsSidebar source={source} />,
...componentStory,
parameters: { ...componentStory.parameters },
} satisfies Meta<SavedViewsArgs>;
export default meta;
type Story = StoryObj<SavedViewsArgs>;
/** Traces views created by the user and by others, with no view open. */
export const Default: Story = {};
/** A workspace with no views yet: the empty state offers Create view. */
export const Empty: Story = {
args: { createdByMe: 0, createdByOthers: 0 },
};
/**
* A view open with a query that differs from what it saved: the name carries
* the unsaved dot, and ✓ and discard replace Clear view.
*/
export const UnsavedChanges: Story = {
args: { openView: 'unsaved-changes' },
};
/**
* The first row's menu. Edit details and Delete follow `update` and `delete` on
* that view, so revoking either disables its item.
*/
export const RowMenu: Story = {
play: async ({ canvasElement }): Promise<void> => {
const [row] = await findRows(canvasElement);
await openRowMenu(row);
},
};
/**
* The ✓ menu on a view with unsaved changes. Save as new view follows `create`
* and Update selected view follows `update` on the open view.
*/
export const SaveChangesMenu: Story = {
args: { openView: 'unsaved-changes' },
play: async ({ canvasElement }): Promise<void> => {
await openSaveChangesMenu(canvasElement);
},
};

View File

@@ -0,0 +1,309 @@
/**
* AI-owned. Generated and maintained by the `signoz-page-story` skill.
* Do not hand-edit: regenerate instead.
*/
import type {
GetSavedView200,
ListSavedViews200,
RenderErrorResponseDTO,
SavedviewtypesSavedViewDTO,
} from 'api/generated/services/sigNoz.schemas';
import {
SavedviewtypesSchemaVersionDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import { QueryParams } from 'constants/query';
import {
initialQueriesMap,
initialQueryMeterWithType,
PANEL_TYPES,
} from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { getSavedViewQuery } from 'container/SavedViews/utils/getSavedViewQuery';
import { toSavedViewSpec } from 'container/SavedViews/utils/toSavedViewSpec';
import { createAppContextMock } from 'tests/fixtures/appContextMock';
import type { Query } from 'types/api/queryBuilder/queryBuilderData';
import { USER_ROLES } from 'types/roles';
export const SAVED_VIEW_SOURCES = [
SavedviewtypesSourceDTO.traces,
SavedviewtypesSourceDTO.logs,
SavedviewtypesSourceDTO.metrics,
SavedviewtypesSourceDTO.meter,
] as const;
export type SavedViewSource = (typeof SAVED_VIEW_SOURCES)[number];
export const OPEN_VIEWS = [
'none',
'created-by-me',
'created-by-others',
'unsaved-changes',
'forbidden',
'failed',
'not-found',
] as const;
export type OpenView = (typeof OPEN_VIEWS)[number];
export const SAVED_VIEWS_MAX = 8;
/** The user the story app context signs in as, so "Created by me" follows it. */
const STORY_USER_EMAIL = createAppContextMock(USER_ROLES.ADMIN).user.email;
const OTHER_AUTHORS = ['priya@signoz.io', 'marco@signoz.io', 'lena@signoz.io'];
const MY_VIEW_NAMES = [
'Checkout errors',
'Slow payment calls',
'Cart service, production',
'Auth retries',
'Inventory sync',
'Search p99 regressions',
'Webhook failures',
'Cold starts after deploy',
];
const OTHER_VIEW_NAMES = [
'On-call triage',
'Frontend 5xx',
'Shipping latency by region',
'Kafka consumer lag',
'Staging smoke tests',
'Recommendation timeouts',
'Ad service sampling',
'Email queue backlog',
];
const EXPRESSIONS = [
"service.name = 'checkout' AND has_error = true",
"service.name = 'payments'",
"service.name = 'cart' AND deployment.environment = 'production'",
"service.name = 'auth'",
"service.name IN ('inventory', 'catalog')",
"service.name = 'search'",
"service.name = 'webhooks' AND has_error = true",
"deployment.environment = 'production'",
];
const BASE_QUERY: Record<SavedViewSource, Query> = {
traces: initialQueriesMap.traces,
logs: initialQueriesMap.logs,
metrics: initialQueriesMap.metrics,
meter: initialQueryMeterWithType,
};
const EXPLORER_ROUTE: Record<SavedViewSource, string> = {
traces: ROUTES.TRACES_EXPLORER,
logs: ROUTES.LOGS_EXPLORER,
metrics: ROUTES.METRICS_EXPLORER_EXPLORER,
meter: ROUTES.METER_EXPLORER,
};
const queryWith = (source: SavedViewSource, expression: string): Query => {
const base = BASE_QUERY[source];
return {
...base,
builder: {
...base.builder,
queryData: [
{
...base.builder.queryData[0],
filter: { expression },
aggregations: [{ expression: 'count()' }],
},
],
},
} as Query;
};
type Author = 'mine' | 'others';
const viewId = (
source: SavedViewSource,
author: Author,
index: number,
): string => `storybook-${source}-${author}-${index + 1}`;
/** The view other people made that the per-view read denial applies to. */
export const unreadableViewId = (source: SavedViewSource): string =>
viewId(source, 'others', 0);
const savedView = (
source: SavedViewSource,
author: Author,
index: number,
): SavedviewtypesSavedViewDTO => {
const displayName = (author === 'mine' ? MY_VIEW_NAMES : OTHER_VIEW_NAMES)[
index
];
const createdBy =
author === 'mine'
? STORY_USER_EMAIL
: OTHER_AUTHORS[index % OTHER_AUTHORS.length];
const id = viewId(source, author, index);
return {
id,
name: id,
source,
schemaVersion: SavedviewtypesSchemaVersionDTO.v2,
createdAt: '2026-09-02T08:30:00Z',
createdBy,
updatedAt: `2026-10-${String(14 - (index % 10)).padStart(2, '0')}T11:42:00Z`,
updatedBy: createdBy,
spec: toSavedViewSpec({
query: queryWith(
source,
EXPRESSIONS[(index + (author === 'mine' ? 0 : 3)) % EXPRESSIONS.length],
),
panelType: PANEL_TYPES.LIST,
displayName,
}),
};
};
const savedViews = (
source: SavedViewSource,
author: Author,
count: number,
): SavedviewtypesSavedViewDTO[] =>
Array.from({ length: Math.min(count, SAVED_VIEWS_MAX) }, (_, index) =>
savedView(source, author, index),
);
export const savedViewsListResponse = (
source: SavedViewSource,
createdByMe: number,
createdByOthers: number,
): ListSavedViews200 => ({
status: 'success',
data: [
...savedViews(source, 'mine', createdByMe),
...savedViews(source, 'others', createdByOthers),
],
});
/** Every view the workspace has, whatever the list control trims it to. */
export const savedViewById = (
id: string,
): SavedviewtypesSavedViewDTO | undefined =>
SAVED_VIEW_SOURCES.flatMap((source) => [
...savedViews(source, 'mine', SAVED_VIEWS_MAX),
...savedViews(source, 'others', SAVED_VIEWS_MAX),
]).find((view) => view.id === id);
/** A view the panel itself created, owned by the story user. */
export const createdSavedView = (
source: SavedViewSource,
id: string,
spec: SavedviewtypesSavedViewDTO['spec'],
): SavedviewtypesSavedViewDTO => ({
...savedView(source, 'mine', 0),
id,
name: id,
createdAt: '2026-10-16T09:00:00Z',
updatedAt: '2026-10-16T09:00:00Z',
spec,
});
export const savedViewResponse = (
view: SavedviewtypesSavedViewDTO,
): GetSavedView200 => ({ status: 'success', data: view });
export const OPEN_VIEW_ERRORS: Partial<
Record<OpenView, { status: number; body: RenderErrorResponseDTO }>
> = {
forbidden: {
status: 403,
body: {
status: 'error',
error: {
code: 'forbidden',
message: 'You do not have access to this saved view',
errors: [],
suggestions: [],
type: 'forbidden',
},
},
},
failed: {
status: 500,
body: {
status: 'error',
error: {
code: 'internal',
message: 'Failed to load the saved view',
errors: [],
suggestions: [],
type: 'internal',
},
},
},
'not-found': {
status: 404,
body: {
status: 'error',
error: {
code: 'not_found',
message: 'Saved view not found',
errors: [],
suggestions: [],
type: 'not_found',
},
},
},
};
const openViewId = (source: SavedViewSource, open: OpenView): string | null => {
if (open === 'none') {
return null;
}
if (open === 'created-by-others' || open === 'forbidden') {
return viewId(source, 'others', 0);
}
if (open === 'not-found') {
return `storybook-${source}-deleted`;
}
return viewId(source, 'mine', 0);
};
/**
* The explorer url with a view open, its params written the way
* `redirectWithQueryBuilderData` writes them. `panelTypes` is left out: the
* query builder seeds its panel type from the raw, JSON-quoted param, which no
* saved panel type equals, and without it the header compares against `list`.
*/
export const savedViewRoute = (
source: SavedViewSource,
open: OpenView,
): string => {
const params = new URLSearchParams({ relativeTime: '15m' });
const id = openViewId(source, open);
if (id) {
const view = savedViewById(id);
const query = view
? getSavedViewQuery(view)
: queryWith(source, EXPRESSIONS[0]);
const staged =
open === 'unsaved-changes'
? queryWith(source, `${EXPRESSIONS[0]} AND duration_nano > 500000000`)
: query;
params.set(
QueryParams.compositeQuery,
encodeURIComponent(
JSON.stringify({ ...staged, id: `storybook-staged-${open}` }),
),
);
params.set(QueryParams.viewKey, JSON.stringify(id));
}
return `${EXPLORER_ROUTE[source]}?${params.toString()}`;
};

View File

@@ -0,0 +1,220 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { expect, waitFor, within } from 'storybook/test';
import { storyMocks } from '@/storybook/controls/defineStoryMocks';
import type { PageStoryArgs } from '@/storybook/runtime/resolveStory';
import { unreadableViewId } from '../__story_mockdata__/savedViews';
import {
byTestId,
expectDisabled,
expectPending,
findRows,
openRowMenu,
openSaveChangesMenu,
rowMenuItem,
rowName,
saveChangesItem,
SavedViewsSidebar,
savedViewsMocks,
sidebarCanvas,
UNTIL_LOADED,
} from '../SavedViews.stories.mocks';
import SavedViewsPanel from '../../SavedViewsPanel';
type SavedViewsArgs = PageStoryArgs<typeof savedViewsMocks>;
const componentStory = storyMocks(savedViewsMocks);
/**
* The saved views sidebar with one permission missing at a time. Every story
* here is an admin denied exactly what its name says. `list` and `create` are
* workspace-wide; `read`, `update` and `delete` are checked per view, so a row
* menu only checks its view once it is opened.
*
* Route: the explorer of the Source control, with the Open view control's
* `viewKey`.
*/
const meta = {
title: 'Components/Saved Views/Authz',
tags: ['authz', 'play'],
component: SavedViewsPanel,
decorators: [sidebarCanvas],
render: ({ source }): JSX.Element => <SavedViewsSidebar source={source} />,
...componentStory,
parameters: { ...componentStory.parameters },
} satisfies Meta<SavedViewsArgs>;
export default meta;
type Story = StoryObj<SavedViewsArgs>;
const LIST = 'list:saved-view';
const CREATE = 'create:saved-view';
const UPDATE = 'update:saved-view';
const DELETE = 'delete:saved-view';
/**
* The panel shows the permission callout in place of the list, and All views,
* in an explorer whose quick filters hide the list, is disabled.
*/
export const NoList: Story = {
args: { revoked: [LIST] },
render: ({ source }): JSX.Element => (
<SavedViewsSidebar source={source} onOpenViews={(): void => {}} />
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expectDisabled(byTestId('saved-views-open'));
await waitFor(
() =>
expect(canvas.getByTestId('saved-views-panel')).toHaveTextContent(
/is not authorized to perform list/,
),
UNTIL_LOADED,
);
await expect(canvas.queryByTestId('saved-views-search')).toBeNull();
},
};
/** An empty workspace with both ways to create a view disabled: + and Create view. */
export const NoCreate: Story = {
args: { revoked: [CREATE], createdByMe: 0, createdByOthers: 0 },
play: async () => {
await expectDisabled(byTestId('saved-views-create'));
await expectDisabled(byTestId('saved-views-list-create'));
},
};
/** The ✓ menu on unsaved changes: Save as new view disabled, Update selected view open. */
export const NoCreateSaveMenu: Story = {
args: { revoked: [CREATE], openView: 'unsaved-changes' },
play: async ({ canvasElement }) => {
await openSaveChangesMenu(canvasElement);
await expectDisabled(saveChangesItem('save-as-new'));
await expectDisabled(saveChangesItem('update'), false);
},
};
/**
* `read` denied on one view made by someone else: its name is disabled and its
* hover card never opens, while every other view still opens.
*/
export const NoRead: Story = {
args: { unreadableView: true },
play: async ({ canvasElement, args }) => {
const rows = await findRows(canvasElement);
const deniedId = unreadableViewId(args.source);
await Promise.all(
rows.map((row) =>
expectDisabled(rowName(row), row.getAttribute('data-view-id') === deniedId),
),
);
},
};
/** The unreadable view open from its link: the header names it No view access. */
export const NoReadOpenView: Story = {
args: { unreadableView: true, openView: 'created-by-others' },
play: async ({ canvasElement }) => {
await expect(
await within(canvasElement).findByText(
'No view access',
undefined,
UNTIL_LOADED,
),
).toBeVisible();
},
};
/** A row's menu: Edit details disabled; Copy link, Copy ID and Delete open. */
export const NoUpdate: Story = {
args: { revoked: [UPDATE] },
play: async ({ canvasElement }) => {
const [row] = await findRows(canvasElement);
await openRowMenu(row);
await expectDisabled(rowMenuItem('editDetails'));
await expectDisabled(rowMenuItem('copyLink'), false);
await expectDisabled(rowMenuItem('copyId'), false);
await expectDisabled(rowMenuItem('delete'), false);
},
};
/** The ✓ menu on unsaved changes: Update selected view disabled, Save as new view open. */
export const NoUpdateSaveMenu: Story = {
args: { revoked: [UPDATE], openView: 'unsaved-changes' },
play: async ({ canvasElement }) => {
await openSaveChangesMenu(canvasElement);
await expectDisabled(saveChangesItem('update'));
await expectDisabled(saveChangesItem('save-as-new'), false);
},
};
/** A row's menu: Delete disabled, Edit details still open. */
export const NoDelete: Story = {
args: { revoked: [DELETE] },
play: async ({ canvasElement }) => {
const [row] = await findRows(canvasElement);
await openRowMenu(row);
await expectDisabled(rowMenuItem('delete'));
await expectDisabled(rowMenuItem('editDetails'), false);
},
};
/**
* Everything but listing and reading: + is disabled, and a row's menu has only
* Copy link and Copy ID left.
*/
export const ReadOnly: Story = {
args: { revoked: [CREATE, UPDATE, DELETE] },
play: async ({ canvasElement }) => {
await expectDisabled(byTestId('saved-views-create'));
const [row] = await findRows(canvasElement);
await expectDisabled(rowName(row), false);
await openRowMenu(row);
await expectDisabled(rowMenuItem('editDetails'));
await expectDisabled(rowMenuItem('delete'));
await expectDisabled(rowMenuItem('copyLink'), false);
await expectDisabled(rowMenuItem('copyId'), false);
},
};
/**
* The permission check never answering: + spins, and the list stays blank
* behind its `list` check.
*/
export const CheckLoading: Story = {
args: { authzState: 'loading' },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expectPending(byTestId('saved-views-create'));
await expect(canvas.queryByTestId('saved-views-search')).toBeNull();
},
};
/**
* The permission check failing rather than denying: the sidebar fails open,
* so the list renders and every action stays enabled for the API to refuse.
*/
export const CheckFailed: Story = {
args: { authzState: 'error' },
parameters: { allowConsoleErrors: true },
play: async ({ canvasElement }) => {
await expectDisabled(byTestId('saved-views-create'), false);
const [row] = await findRows(canvasElement);
await expectDisabled(rowName(row), false);
await openRowMenu(row);
await expectDisabled(rowMenuItem('editDetails'), false);
await expectDisabled(rowMenuItem('delete'), false);
},
};

View File

@@ -1,5 +1,6 @@
import { ReactElement, ReactNode } from 'react';
import type { Link } from '@signozhq/icons';
import type { BrandedPermission } from 'lib/authz/hooks/useAuthZ/types';
import {
SavedviewtypesSavedViewDTO,
SavedviewtypesSourceDTO,
@@ -41,6 +42,8 @@ export interface UseActiveSavedViewResult {
view: SavedviewtypesSavedViewDTO | undefined;
isLoading: boolean;
isError: boolean;
isReadDenied: boolean;
errorMessage: string | undefined;
hasUnsavedChanges: boolean;
}
@@ -70,6 +73,7 @@ export interface SaveViewModalProps {
}
export interface SaveChangesMenuProps {
viewId: string;
disabled: boolean;
onSaveAsNew: () => void;
onUpdate: () => void;
@@ -82,6 +86,7 @@ export interface SavedViewsIconButtonProps {
color?: 'secondary' | 'warning';
disabled?: boolean;
disabledTooltip?: ReactNode;
checks?: BrandedPermission[];
onClick?: () => void;
}
@@ -116,7 +121,11 @@ export interface SavedViewsListRowProps {
hover: SavedViewsListRowHoverProps;
}
export type SavedViewRowActionKey = 'copyLink' | 'editDetails' | 'delete';
export type SavedViewRowActionKey =
| 'copyLink'
| 'copyId'
| 'editDetails'
| 'delete';
export interface SavedViewRowActionConfig {
key: SavedViewRowActionKey;
@@ -124,10 +133,11 @@ export interface SavedViewRowActionConfig {
icon: typeof Link;
danger?: boolean;
hasDividerBefore?: boolean;
buildPermission?: (id: string) => BrandedPermission;
}
export interface PendingSavedViewAction {
key: Exclude<SavedViewRowActionKey, 'copyLink'>;
key: Exclude<SavedViewRowActionKey, 'copyLink' | 'copyId'>;
view: SavedviewtypesSavedViewDTO;
}
@@ -140,6 +150,7 @@ export interface SavedViewsRowButtonProps {
}
export interface SavedViewsRowMenuProps {
viewId: string;
onAction: (key: SavedViewRowActionKey) => void;
onOpen: () => void;
}
@@ -152,6 +163,7 @@ export interface DeleteSavedViewDialogProps {
export interface UseSavedViewRowActionsResult {
copyViewLink: (view: SavedviewtypesSavedViewDTO) => void;
copyViewId: (view: SavedviewtypesSavedViewDTO) => void;
renameView: (
view: SavedviewtypesSavedViewDTO,
displayName: string,

View File

@@ -62,25 +62,21 @@ export const getAbbreviatedLabel = (label: string): string => {
export const showAllDataSetFromApiResponse = (
apiResponse: QueryData[],
): LegendEntryProps[] =>
apiResponse.map(
(item): LegendEntryProps => ({
label: getLabelName(
item.metric || {},
item.queryName || '',
item.legend || '',
),
show: true,
}),
);
apiResponse.map((item): LegendEntryProps => ({
label: getLabelName(
item.metric || {},
item.queryName || '',
item.legend || '',
),
show: true,
}));
export const showAllDataSet = (options: uPlot.Options): LegendEntryProps[] =>
options.series
.map(
(item): LegendEntryProps => ({
label: item.label || '',
show: true,
}),
)
.map((item): LegendEntryProps => ({
label: item.label || '',
show: true,
}))
.filter((_, index) => index !== 0);
export const saveLegendEntriesToLocalStorage = ({
@@ -90,12 +86,10 @@ export const saveLegendEntriesToLocalStorage = ({
}: SaveLegendEntriesToLocalStoreProps): void => {
const newLegendEntry = {
name,
dataIndex: options.series.map(
(item, index): LegendEntryProps => ({
label: item.label || '',
show: graphVisibilityState[index],
}),
),
dataIndex: options.series.map((item, index): LegendEntryProps => ({
label: item.label || '',
show: graphVisibilityState[index],
})),
};
let existingEntries: { name: string; dataIndex: LegendEntryProps[] }[] = [];

View File

@@ -1,3 +1,4 @@
import type { ExtendedSeries } from 'lib/uPlotV2/config/types';
import { Widgets } from 'types/api/widgets/widget';
import {
MetricRangePayloadProps,
@@ -164,6 +165,27 @@ describe('BarPanel utils', () => {
expect(config.series).toHaveLength(3);
});
it('passes partial timestamps to the series', () => {
const apiResponse = createApiResponse([
{
metric: {},
queryName: 'Q1',
values: [
[1000, '1'],
[1060, '0.2'],
],
partialTimestamps: [1060],
} as MetricRangePayloadProps['data']['result'][0],
]);
const series = prepareBarPanelConfig({
...baseParams,
apiResponse,
}).getConfig().series?.[1] as ExtendedSeries;
expect(series.partialTimestamps).toStrictEqual(new Set([1060]));
});
it('uses getLegend for label when currentQuery is provided', () => {
const apiResponse = createApiResponse([
{

View File

@@ -89,6 +89,9 @@ export function prepareBarPanelConfig({
isDarkMode,
stepInterval: currentStepInterval,
metric: series.metric,
partialTimestamps: series.partialTimestamps
? new Set(series.partialTimestamps)
: undefined,
});
});

View File

@@ -5,6 +5,7 @@ import {
} from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import type { ExtendedSeries } from 'lib/uPlotV2/config/types';
import { PanelMode } from 'lib/visualization/panels/types';
import { prepareUPlotConfig } from 'container/WidgetCard/Panels/TimeSeriesPanel/utils';
import { prepareChartData } from 'lib/uPlotV2/utils/dataUtils';
@@ -185,6 +186,28 @@ describe('TimeSeriesPanel utils', () => {
});
});
it('passes partial timestamps to the series', () => {
const apiResponse = createApiResponse([
{
metric: {},
queryName: 'Q1',
values: [
[1000, '1'],
[1060, '0.2'],
],
partialTimestamps: [1060],
} as MetricRangePayloadProps['data']['result'][0],
]);
const series = prepareUPlotConfig({
...baseParams,
apiResponse,
currentQuery: null as unknown as Query,
}).getConfig().series?.[1] as ExtendedSeries;
expect(series.partialTimestamps).toStrictEqual(new Set([1060]));
});
it('uses getLegend for label when currentQuery is provided', () => {
const apiResponse = createApiResponse([
{

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