Compare commits

..

112 Commits

Author SHA1 Message Date
Naman Verma
f311fcabf7 feat: v2 dashboard update API 2026-04-29 18:39:48 +05:30
Naman Verma
a37c07f881 feat: v2 dashboard GET API 2026-04-29 15:12:47 +05:30
Naman Verma
4d9386f418 fix: merge conflicts 2026-04-29 14:36:39 +05:30
Naman Verma
737473521d Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-04-29 14:33:25 +05:30
Naman Verma
1863db8ba8 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-04-29 14:32:14 +05:30
Naman Verma
661af09a13 Merge branch 'main' into nv/dashboardv2 2026-04-29 14:31:59 +05:30
Naman Verma
6024fa2b91 fix: remove extra spec from builder query marshalling 2026-04-29 14:31:16 +05:30
Naman Verma
8996a96387 chore: use existing mapper 2026-04-29 14:09:34 +05:30
Naman Verma
d6db5c2aab test: integration test fixes 2026-04-29 12:56:14 +05:30
Abhi kumar
f4e5534e53 chore: updated drilldown popup ui to match tooltip (#11113) 2026-04-29 06:55:01 +00:00
Naman Verma
709590ea1b test: integration tests for create API 2026-04-29 12:23:12 +05:30
swapnil-signoz
14a032119a chore: bumping cloud integration agent version to v0.0.10 (#11135)
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
Release Drafter / update_release_draft (push) Has been cancelled
* chore: bumping agent version to v0.0.10

* chore: depployment
2026-04-29 05:23:22 +00:00
Yunus M
e78dfc1622 Azure service integration UI (#11117)
* feat: azure integration - ui refactor

* feat: implement AWS cloud account integration UI components and connection handling

* feat: add Azure cloud account integration UI components and connection handling

* feat: enhance Azure cloud account setup UI with prerequisites and accordion for how it works section

* feat: enhance styling for Azure and AWS account management

* refactor: clean up state initialization and destructuring in AWS and HeroSection components

* fix: update import path for ServiceDashboards in S3Sync test

* feat: add Denmark East region to Azure regions and enhance Azure account removal messaging

* chore: remove prefer-signoz-ui-icons ESLint rule and update telemetry event naming
2026-04-29 03:58:38 +00:00
Prakhar Dewan
4df96ae5dd chore: remove unused assets files (#11045)
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
Release Drafter / update_release_draft (push) Has been cancelled
2026-04-28 21:06:56 +00:00
Prakhar Dewan
705ff439c4 chore: remove unused api files (#11041)
Co-authored-by: Vinicius Lourenço <12551007+H4ad@users.noreply.github.com>
2026-04-28 20:37:55 +00:00
Yunus M
c230fbcbc2 fix: implement tag filter mapping to canonical and deprecated operators (#11124)
Co-authored-by: Piyush Singariya <piyushsingariya@gmail.com>
2026-04-28 17:52:27 +00:00
swapnil-signoz
69212ef9b1 feat(cloudintegrations): add azure dashboards (#11094)
* refactor: moving types to cloud provider specific namespace/pkg

* refactor: separating cloud provider types

* refactor: using upper case key for AWS

* feat: adding cloud integration azure types

* feat: adding azure services

* refactor: updating omitempty tags

* refactor: updating azure integration config

* feat: completing azure types

* refactor: lint issues

* feat: adding service definitions for azure

* refactor: update service names for Azure Blob Storage telemetry

* refactor: updating definitions with metrics and strategy

* refactor: updating command key

* fix: handle optional connection URL in AWS integration

* feat: wip

* refactor: updating strategy struct

* refactor: updating telemetry strategy

* refactor: updating connection artifact struct

* refactor: updating blob storage service name

* refactor: updating azure blob storage service name

* refactor: update Azure service identifiers

* refactor: updating service defs

* fix: update integration account ID and add agent version to Azure CLI and PowerShell commands

* refactor: updating deny settings mode

* refactor: updating types

* refactor: adding missing case for azure service update

* feat: implement Azure connection commands and add unit tests

* refactor: using template for Azure connection artifact creation and update tests

* feat: adding dashboards for azure

* refactor: updated aggregrations

* refactor: updating aggregations
2026-04-28 17:46:29 +00:00
Nikhil Soni
9a3e79fb54 Add support for custom field selection for color by feature. (#11092)
* feat: add customer aggregation support in waterfall

* chore: add tests for aggregation logic

* chore: rename analytics to aggregations

* chore: update openapi specs

* feat: add support to request telemetry fields in flamegraph

* chore: simplify getting attribute value for span

* fix: remove overlapping time from duration aggregation

* fix: use valuer.String for enums

* feat: add preferences for preview and color by
2026-04-28 15:34:31 +00:00
Piyush Singariya
584bf2fe74 chore: add json enabled as feature flag for FE (#11050)
* chore: add json enabled as feature flag for FE

* fix: still using global bool

* feat: flagger integration in flow

* fix: flagger threaded into tests

* test: removed nil checks

* fix: minor changes

* chore: rename field

* chore: remove querybuilder helper

* fix: unit tests

* fix: correct env var

* fix: lint fix

* fix: lint

* chore: replace flag
2026-04-28 15:29:46 +00:00
Naman Verma
1add46b4c5 fix: module should also validate postable dashboard 2026-04-28 20:05:38 +05:30
Naman Verma
8401261e20 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-04-28 20:04:33 +05:30
Naman Verma
0ff34a7274 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-04-28 20:04:08 +05:30
Naman Verma
44e3bd9608 chore: separate method for validation 2026-04-28 20:03:48 +05:30
Naman Verma
c3944d779e fix: more dashboard request validations 2026-04-28 19:59:11 +05:30
Naman Verma
f5ec783a53 fix: go lint fix 2026-04-28 19:33:28 +05:30
Naman Verma
35b729c425 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-04-28 19:30:42 +05:30
Naman Verma
4f43c3d803 fix: use existing tag's casing if new tag is a prefix of an existing tag 2026-04-28 19:30:07 +05:30
Naman Verma
5dbde6c64d fix: only return name of a tag in dashboard response 2026-04-28 19:13:03 +05:30
Pandey
738ae6f21b docs: add servers block to the openapi spec (#11129)
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
Release Drafter / update_release_draft (push) Has been cancelled
* docs: add servers block to the openapi spec

* docs: add info block
2026-04-28 12:32:35 +00:00
SagarRajput-7
be9dd7de0e feat(billing-page): added cancel subscription option in billing page (#11118)
* feat(billing-page): added cancel subscription option in billing page

* feat(billing-page): semantic token correction

* feat(billing-page): added test cases

* feat(billing-page): used css module

* feat(billing-page): added license condition and log event

* feat(billing-page): added test cases

* feat(billing-page): addressed design feedbacks
2026-04-28 11:50:19 +00:00
Nikhil Mantri
ffd493617f feat(infra-monitoring): v2 pods list API (#10833)
* chore: baseline setup

* chore: endpoint detail update

* chore: added logic for hosts v3 api

* fix: bug fix

* chore: disk usage

* chore: added validate function

* chore: added some unit tests

* chore: return status as a string

* chore: yarn generate api

* chore: removed isSendingK8sAgentsMetricsCode

* chore: moved funcs

* chore: added validation on order by

* chore: added pods list logic

* chore: updated openapi yml

* chore: updated spec

* chore: pods api meta start time

* chore: nil pointer check

* chore: nil pointer dereference fix in req.Filter

* chore: added temporalities of metrics

* chore: added pods metrics temporality

* chore: unified composite key function

* chore: code improvements

* chore: added pods list api updates

* chore: hostStatusNone added for clarity that this field can be left empty as well in payload

* chore: yarn generate api

* chore: return errors from getMetadata and lint fix

* chore: return errors from getMetadata and lint fix

* chore: added hostName logic

* chore: modified getMetadata query

* chore: add type for response and files rearrange

* chore: warnings added passing from queryResponse warning to host lists response struct

* chore: added better metrics existence check

* chore: added a TODO remark

* chore: added required metrics check

* chore: distributed samples table to local table change for get metadata

* chore: frontend fix

* chore: endpoint correction

* chore: endpoint modification openapi

* chore: escape backtick to prevent sql injection

* chore: rearrage

* chore: improvements

* chore: validate order by to validate function

* chore: improved description

* chore: added TODOs and made filterByStatus a part of filter struct

* chore: ignore empty string hosts in get active hosts

* feat(infra-monitoring): v2 hosts list - return counts of active & inactive hosts for custom group by attributes (#10956)

* chore: add functionality for showing active and inactive counts in custom group by

* chore: bug fix

* chore: added subquery for active and total count

* chore: ignore empty string hosts in get active hosts

* fix: sinceUnixMilli for determining active hosts compute once per request

* chore: refactor code

* chore: rename HostsList -> ListHosts

* chore: rearrangement

* chore: inframonitoring types renaming

* chore: added types package

* chore: file structure further breakdown for clarity

* chore: comments correction

* chore: removed temporalities

* chore: pods code restructuring

* chore: comments resolve

* chore: added json tag required: true

* chore: removed pod metric temporalities

* chore: removed internal server error

* chore: added status unauthorized

* chore: remove a defensive nil map check, the function ensure non-nil map when err nil

* chore: cleanup and rename

* chore: make sort stable in case of tiebreaker by comparing composite group by keys

* chore: regen api client for inframonitoring

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: added required tags

* chore: added support for pod phase unknown

* chore: removed pods - order by phase

* chore: improved api description to document -1 as no data in numeric fields

* fix: rebase fixes

* feat(infra-monitoring): v2 pods list apis - phase counts when custom grouping (#11088)

* chore: added phase counts feature

* chore: added queries for pod phase counts in custom group by

* chore: added unknown phase count

* fix: isPodUIDInGroupBy in buildPodRecords

* chore: 3 cte --> 2 cte

* chore: pod phase with local table of time series as counts

* chore: comment correction

* chore: corrected comment

* chore: value column for samples table added

* chore: removed query G for phase counts

* chore: rename variable

* chore: added PodPhaseNum constants to types

* chore: updated comment

* chore: formatted file

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ashwin Bhatkal <ashwin96@gmail.com>
2026-04-28 10:19:26 +00:00
Pandey
3f95d35ad5 chore: add @therealpandey with @srikanthcvv (#11126)
#### Temporary

- add @therealpandey with @srikanthcvv
2026-04-28 10:01:39 +00:00
Naman Verma
fb6fdd54ec feat: v2 create dashboard API 2026-04-28 15:05:29 +05:30
Naman Verma
64b8ba62da Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-04-28 15:04:11 +05:30
Naman Verma
7c66df408b Merge branch 'main' into nv/dashboardv2 2026-04-28 15:04:03 +05:30
Naman Verma
54049de391 chore: follow proper unmarshal json method structure 2026-04-28 15:02:49 +05:30
Piyush Singariya
9e5678d6b3 CI: JSON QB E2E Integration Suite (#10863)
* feat: enable JSON Path index

* fix: contextual path index usage

* test: fix unit tests

* feat: align negative operators to include other logs

* fix: primitive conditions working

* fix: indexed tests passing

* fix: array type filtering from dynamic arrays

* fix: unit tests

* fix: remove not used paths from testdata

* fix: unit tests

* fix: comment

* fix: indexed unit tests

* fix: array json element comparison

* feat: change filtering of dynamic arrays

* fix: dynamic array tests

* fix: stringified integer value input

* fix: better review for test file

* fix: negative operator check

* ci: lint changes

* chore: import tests from older pr

* fix: better tests

* fix: logs.py

* fix: body tests ready

* fix: better validations

* fix: dynamically change insert stmt for body_v2 availability

* test: with higher migrator version

* fix: type ambiguity

* fix: test

* test: updated validation

* fix: tons of changes

* chore: remove redundent comparison

* ci: tests fixed

* fix: upgraded collector version

* fix: qbtoexpr tests

* fix: go sum

* chore: upgrade collector version v0.144.3-rc.4

* fix: tests

* ci: test fix

* revert: remove db binaries

* test: selectField tests added

* fix: added safeguards in plan generation

* fix: name changed to field_map

* chore: changes based on review

* fix: changes based on review

* fix: remove unused promoted fixture

* test: integration test fix attempt 1

* fix: json access plan remval of AvailableTypes

* fix: invalid index usage on terminal condition

* test: added indexed tests separately

* test: select order by tests added

* fix: update jsontypeexporter

* fix: branches should tell missing array types

* fix: comment removed

* ci: test with updated collector

* fix: issue with FuzzyMatching and API failing

* test: select orderby works

* fix: int64 mapping

* fix: replacing JSONIndex with TelemetryFieldKeyIndex

* chore: update collector to stable release

* chore: update migrator version to stable release

* chore: error fix and unused code

* chore: go mod tidy

* chore: some changes in test to improve quality

* test: enhanced tests to work with backtick required key

* chore: comment removal

* fix: change based on review

* fix: change based on review

* fix: openapi ci

* ci: fix go tests

* fix: index needs body prefix

* fix: tests

* ci: polluted test fix

* fix: shift test files

* refactor(telemetrytypes): replace JSONDataTypeIndex with TelemetryFieldKeySkipIndex

* revert: mcp related changes

* feat: check query log as fixture

* fix: reuse querier fixtures

* chore: rename jsontypeexporter.py

* chore: py-fmt

* fix: minor fix

* fix: py-lint

* fix: changes based on review

* chore: fmt
2026-04-28 08:49:44 +00:00
Abhi kumar
941016f12c Feat/crosshair series highlight (#11013)
* chore: added changes for crosshair sync for tooltip

* chore: minor cleanup

* chore: updated the core structure

* chore: updated the types

* chore: minor cleanup

* feat: added changes for sereis highlighting on crosshair sync

* chore: pr review fixes

* chore: handled other cases of groupby

* chore: minor changes

* feat: tooltip sync across panels (#11114)

* feat: added changes for syncing tooltip

* fix: fixed other tooltips closing when clicked on top of root tooltip

* fix: highlighting first series

* chore: removed y-axis sync for tooltip mode

* chore: minor fix

* chore: fmt fix
2026-04-28 06:52:51 +00:00
Vinicius Lourenço
500ce85ccb chore(oxlint): run fixes (#11123)
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
Release Drafter / update_release_draft (push) Has been cancelled
* chore(oxlint): run fixes

* test(to-strict): failing due to missing props
2026-04-28 04:27:45 +00:00
Naman Verma
a82f4237c8 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-04-28 09:52:23 +05:30
Naman Verma
89606b6238 Merge branch 'main' into nv/dashboardv2 2026-04-28 09:52:13 +05:30
Naman Verma
db5ce958eb Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-04-28 09:49:01 +05:30
Naman Verma
c8d3a9a54b feat: enum for entity type that other modules can register 2026-04-28 09:47:24 +05:30
Prakhar Dewan
d1c9864f52 chore: remove unused files (#11033)
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
Release Drafter / update_release_draft (push) Has been cancelled
* chore: remove unused files

* chore: add knip.json file
2026-04-27 19:23:12 +00:00
Naman Verma
637870b1fc feat: define tags module for v2 dashboard creation 2026-04-27 22:14:47 +05:30
Naman Verma
d46a7e24c9 Merge branch 'main' into nv/dashboardv2 2026-04-27 22:12:12 +05:30
Manika Malhotra
f80b650390 chore(onboarding): add open-source tooling to interest in signoz option (#11083) 2026-04-27 14:55:09 +00:00
Naman Verma
2a451e1c31 test: test for drift detection mechanics 2026-04-27 18:57:41 +05:30
Naman Verma
60b6d1d890 chore: better method name extractKindAndSpec 2026-04-27 18:42:31 +05:30
Naman Verma
36f755b232 chore: cleanup comments 2026-04-27 18:39:41 +05:30
Naman Verma
c1b3e3683a chore: code movement 2026-04-27 18:37:57 +05:30
Naman Verma
4c68544b1a chore: go lint fix (godot) 2026-04-27 18:37:05 +05:30
Naman Verma
90d9ab95f9 chore: code movement 2026-04-27 18:35:18 +05:30
Naman Verma
065e712e0c chore: code movement 2026-04-27 18:33:48 +05:30
Naman Verma
50db309ecd chore: code movement 2026-04-27 18:32:41 +05:30
Naman Verma
261bc552b0 chore: cleanup testing code 2026-04-27 18:24:52 +05:30
Naman Verma
bab720e98b Merge branch 'main' into nv/dashboardv2 2026-04-27 18:21:09 +05:30
Naman Verma
71fef6636b chore: better method name 2026-04-27 18:18:14 +05:30
Naman Verma
fc3cdecbbb chore: cleaner comment 2026-04-27 18:15:21 +05:30
Naman Verma
860fcfa641 chore: cleaner comment 2026-04-27 18:14:27 +05:30
Naman Verma
a090e3a4aa chore: cleaner comment 2026-04-27 18:14:02 +05:30
Naman Verma
6cf73e2ade chore: better comment to explain what restrictKindToLiteral does 2026-04-27 18:13:34 +05:30
Naman Verma
bbcb6a45d6 chore: renames and code rearrangement 2026-04-27 17:53:54 +05:30
Naman Verma
d13934febc fix: remove textbox plugin from openapi spec 2026-04-27 17:29:36 +05:30
Naman Verma
d5a7b7523d fix: strict decode variable spec as well 2026-04-27 17:27:51 +05:30
Naman Verma
5b8984f131 Merge branch 'main' into nv/dashboardv2 2026-04-27 17:18:44 +05:30
Naman Verma
6ddc5f1f12 chore: better error messages 2026-04-27 17:18:11 +05:30
Nityananda Gohain
64c356b484 feat: types and handler for span attribute mapping (ai-o11y) (#10909)
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
Release Drafter / update_release_draft (push) Has been cancelled
* feat: 1.types and handler for ai-o11y attribute mapping

* feat: 1.types and handler for ai-o11y attribute mapping

* fix: cleanup

* fix: minor changes

* fix: remove stutters

* fix: remove lint issues

* fix: lint issues

* fix: address comments

* fix: address comments

* fix: address more comments

* fix: more changes

* fix: address comments

* fix: address comments

* fix: address comments

* fix: use mustnewuuid
2026-04-27 11:45:52 +00:00
Naman Verma
055968bfad fix: dot at the end of a comment 2026-04-27 17:07:58 +05:30
Naman Verma
1bf0f38ed9 fix: js lint errors 2026-04-27 17:07:38 +05:30
swapnil-signoz
97885babe8 feat: adding cloud integration implementation details for Azure (#11058)
* refactor: moving types to cloud provider specific namespace/pkg

* refactor: separating cloud provider types

* refactor: using upper case key for AWS

* feat: adding cloud integration azure types

* feat: adding azure services

* refactor: updating omitempty tags

* refactor: updating azure integration config

* feat: completing azure types

* refactor: lint issues

* feat: adding service definitions for azure

* refactor: update service names for Azure Blob Storage telemetry

* refactor: updating definitions with metrics and strategy

* refactor: updating command key

* fix: handle optional connection URL in AWS integration

* feat: wip

* refactor: updating strategy struct

* refactor: updating telemetry strategy

* refactor: updating connection artifact struct

* refactor: updating blob storage service name

* refactor: updating azure blob storage service name

* refactor: update Azure service identifiers

* refactor: updating service defs

* fix: update integration account ID and add agent version to Azure CLI and PowerShell commands

* refactor: updating deny settings mode

* refactor: updating types

* refactor: adding missing case for azure service update

* feat: implement Azure connection commands and add unit tests

* refactor: using template for Azure connection artifact creation and update tests
2026-04-27 11:23:52 +00:00
Naman Verma
842125e20a chore: too many comments 2026-04-27 16:50:41 +05:30
Naman Verma
6dab35caf8 chore: better file name 2026-04-27 16:43:42 +05:30
Naman Verma
047e9e2001 chore: better file names 2026-04-27 16:42:31 +05:30
Naman Verma
45eaa7db58 test: add tests for spec wrappers 2026-04-27 16:36:27 +05:30
Naman Verma
8a3d894eba chore: comment cleanup 2026-04-27 16:32:29 +05:30
Naman Verma
5239060b53 chore: move plugin maps to correct file 2026-04-27 16:30:33 +05:30
Naman Verma
42c6f507ac test: more descriptive test file name 2026-04-27 15:42:54 +05:30
Naman Verma
1b695a0b80 chore: separate file for perses replicas 2026-04-27 15:42:21 +05:30
Piyush Singariya
78e3916aea chore: replace JSONDataTypeIndex and bump otel-collector to v0.144.3 (#11110)
* refactor(telemetrytypes): replace JSONDataTypeIndex with TelemetryFieldKeySkipIndex

* revert: mcp related changes

* fix: openapi fails

---------

Co-authored-by: Ankit Nayan <ankit@signoz.io>
2026-04-27 15:40:50 +05:30
Naman Verma
438cfab155 chore: comment clean up 2026-04-27 15:39:46 +05:30
Naman Verma
69f7617e01 Merge branch 'main' into nv/dashboardv2 2026-04-27 15:36:58 +05:30
Yunus M
a4266fa703 feat: enable JSON body query support and add group by functionality (#11042)
* feat: enable JSON body query support and add group by functionality

* chore: update the FF
2026-04-27 07:24:03 +00:00
Shivam Gupta
44add7b7cd feat(onboarding): add OpenCode, Baseten, and DBOS datasources (#11109)
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
Release Drafter / update_release_draft (push) Has been cancelled
* feat(onboarding): add OpenCode, Baseten, and DBOS datasources

Adds three new datasources to the onboarding config with logos and
documentation links, closing signoz.io issues #3111, #3053, and #3040.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(onboarding): fix basetenUrl import alphabetical ordering

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 03:11:38 +00:00
Yunus M
feea9e9b36 refactor: remove light mode styles from various components and update… (#11080)
Some checks failed
build-staging / prepare (push) Has been cancelled
Release Drafter / update_release_draft (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
* refactor: remove light mode styles from various components and update color variables

* fix: remove hardcoded background in celery
2026-04-25 06:57:25 +00:00
Ashwin Bhatkal
e94767bda8 refactor(frontend): remove xstate and migrate to plain React state (#11059)
* refactor(frontend): remove xstate and migrate to plain React state

Replace xstate state machines with useState-based step tracking in the
three remaining consumers (labels form, dashboard search filter,
resource attribute provider). Drops xstate and @xstate/react from
dependencies and removes the corresponding no-restricted-imports
entries from oxlint config.

* refactor: clear list of dashboard

* chore: resolve comments
2026-04-25 05:11:39 +00:00
Vishal Sharma
bb10f51cc5 feat(settings): add SigNoz MCP Server setup page (#11025)
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
Release Drafter / update_release_draft (push) Has been cancelled
* feat(settings): add SigNoz MCP Server setup page

Add a new Settings page at /settings/mcp-server that guides Cloud users
through connecting AI assistants (Cursor, VS Code, Claude Desktop, Claude
Code, Codex) to SigNoz via the Model Context Protocol, and provides a
deep-link into Service Accounts for creating the API key the OAuth flow
needs.

* feat(settings): point MCP Server onboarding tile to in-app settings page

The onboarding-config-with-links entry for SigNoz MCP Server previously
linked out to the docs page. Now that we ship an in-product setup page
at /settings/mcp-server, route Cloud users there directly via the
existing internalRedirect pattern. Self-hosted users still see the
in-page fallback card with a docs link.

* fix(settings): fire MCP Server page-viewed event reliably on mount

Previously gated the PAGE_VIEWED analytics event on
isGlobalConfigFetched to avoid a double-fire when the async
ingestion_url resolved. Side effect: if /global/config was slow or
errored out, the event never fired. Fire once on mount instead with
hostname-derived region metadata, which is synchronous and reliable.

* feat(mcp-page): ui refactor and redesign

* feat(mcp-page): global config and page access changes

* feat(mcp-page): refactor and added fallback page

* feat(mcp-page): added test cases

* feat(mcp-page): formatting lint

* feat(mcp-page): code refactor

* feat(mcp-page): cleanup and migrated global config api to open api spec

* feat(mcp-page): removed translation json and add endpoint url to copy

* feat(mcp-page): added mcp server to menu always for cloud and enterprise

---------

Co-authored-by: SagarRajput-7 <sagar@signoz.io>
Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
2026-04-24 21:25:00 +00:00
Vinicius Lourenço
cd16081a1e chore(oxlint): remove unicorn & other rules that breaks the code on --fix (#11097)
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
Release Drafter / update_release_draft (push) Has been cancelled
2026-04-24 17:42:49 +00:00
Naman Verma
4420a7e1fc test: much bigger json for data column 2026-04-24 22:16:03 +05:30
Ashwin Bhatkal
c23a53c8d2 fix: add config path and fmt config path in vs code settings (#11093)
* fix: add config path vs code settings

* fix: add fmt config path as well

* fix: quiet instead of fix
2026-04-24 15:09:30 +00:00
Abhi kumar
dbfe47e757 chore: added tooltip events + minor ui fixes (#11086)
* chore: added tooltip events + minor ui fixes

* chore: pr review comments

* chore: added todo for event removal

* fix: added fix for formatting issues

---------

Co-authored-by: Ashwin Bhatkal <ashwin96@gmail.com>
2026-04-24 13:30:26 +00:00
Ashwin Bhatkal
e29b032e70 test(useResourceAttribute): add ResourceProvider behavior coverage (#11069)
* test(useResourceAttribute): add ResourceProvider behavior coverage

Covers initial state, URL hydration, step-machine transitions (Idle ->
TagKey -> Operator -> TagValue), handleBlur commit/purge paths,
handleClose/handleClearAll, handleEnvironmentChange (add, clear,
replace, dot-metrics feature flag, preserving unrelated URL params),
and SERVICE_MAP visibility filtering.

Tests exercise only the public IResourceAttributeProps contract so they
serve as a behavior pin for any future refactor of the internal state
machine.

* test(useResourceAttribute): close ResourceProvider coverage gaps

Adds four tests covering:
- URL re-hydration effect fires when router URL changes mid-session
- SERVICE_MAP filters fetched tag keys through whilelistedKeys
- In-flight GetTagKeys keeps loading=true and clears optionsData
- API error payload still flips loading=false and leaves options empty

* fix: format file
2026-04-24 12:59:59 +00:00
Naman Verma
b4bc68c5c5 test: data column in perf tests should match real data 2026-04-24 17:17:37 +05:30
Vinicius Lourenço
a92871d704 chore(fmt): enable oxfmt (#11057)
* chore(fmt): enable oxfmt

* fix(prettify): update files due to main
2026-04-24 11:45:54 +00:00
Vinicius Lourenço
b55ae83993 feat(tanstack-table): promote logs table component to /components (#10946)
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
Release Drafter / update_release_draft (push) Has been cancelled
* refactor(tanstack): move table to components & convert to css modules

* refactor(table): extract table to own component

* refactor(table): optimize

* chore(tests): cleanup tests and components

* fix(styles): minor fixes on styles

* fix(tests): use find by text

* fix(row): missing active styles

* refactor(tanstack-table): refine component based on infra monitoring

* docs(tanstacktable): add more docs about usage

* refactor(table): cleanup and fixes related to column and old preferences of logs

* refactor(table): more cleanup

* refactor(table): removed deprecated api

* fix(tanstack): more cleanup

* fix(sonner): removed old dependency

* fix(resizing): better resizing support

* fix(tanstack-header-row): use our version of popover

* fix(column-view): remove unused file

* fix(oxlint): alerts and issues

* fix(test): rollback aria-sort and rename to data-sort
2026-04-24 10:44:13 +00:00
Pandey
7e7d7ab570 feat(global): add mcp_url to global config (#11085)
* feat(global): add mcp_url to global config

Adds an optional mcp_url field to the global config so the frontend can
gate the MCP settings page on its presence. When unset the API returns
"mcp_url": null (pointer + nullable:"true"); when set it emits the
parsed URL as a string.

* feat(global): surface mcp_url in frontend types

Adds mcp_url to the manual GlobalConfigData type and refreshes the
generated OpenAPI client so consumers can read the new field.

* docs(global): use <unset> placeholder for mcp_url example

Matches the style of external_url and ingestion_url above it.

* style(global): separate mcp_url prep from return in GetConfig

Adds a blank line between the nullable-conversion block and the return
statement so the two logical phases read as distinct blocks.

* feat(global): mark endpoint fields as required in the API schema

The backend always emits external_url, ingestion_url and mcp_url on
GET /api/v1/global/config (mcp_url as literal null when unset), so the
JSON keys are always present. Add required:"true" to all three and
regenerate the OpenAPI + frontend client so consumers get non-optional
types.

* revert(global): drop mcp_url from legacy GlobalConfigData type

The legacy hand-written type for the non-Orval getGlobalConfig client
should be left alone; consumers that need mcp_url go through the
generated Orval client.
2026-04-24 10:24:21 +00:00
Nikhil Soni
156459e414 refactor: move waterfall api to module structure with updated response (#10797)
* feat: setup types and interface for waterfall v3

v3 is required for udpating the response json of
the waterfall api. There wont' be any logical change.
Using this requirement as an opportunity to move
waterfall api to provider codebase architecture from
older query-service

* refactor: move type conversion logic to types pkg

* chore: add reason for using snake case in response

* fix: update span.attributes to map of string to any

To support otel format of diffrent types of attributes

* fix: remove unused fields and rename span type

To avoid confusing with otel span

* refactor: convert waterfall api to modules format

* chore: add same test cases as for old waterfall api

* chore: avoid sorting on every traversal

* fix: remove unused fields and rename span type

To avoid confusing with otel span

* fix: rename timestamp to milli for readability

* fix: add timeout to module context

* fix: use typed paramter field in logs

* chore: generate openapi spec for v3 waterfall

* fix: remove timeout since waterfall take longer

* fix: use int16 for status code as per db schema

* fix: update openapi specs

* refactor: break down GetWaterfall method for readability

* chore: avoid returning nil, nil

* refactor: move type creation and constants to types package

- Move DB/table/cache/windowing constants to tracedetailtypes package
- Add NewWaterfallTrace and NewWaterfallResponse constructors in types
- Use constructors in module.go instead of inline struct literals
- Reorder waterfall.go so public functions precede private ones

* refactor: extract ClickHouse queries into a store abstraction

Move GetTraceSummary and GetTraceSpans out of module.go into a
traceStore interface backed by clickhouseTraceStore in store.go.
The module struct now holds a traceStore instead of a raw
telemetrystore.TelemetryStore, keeping DB access separate from
business logic.

* refactor: move error to types as well

* refactor: separate out store calls and computations

* refactor: breakdown GetSelectedSpans for readability

* refactor: return 404 on missing trace and other cleanup

* refactor: use same method for cache key creation

* chore: remove unused duration nano field

* chore: use sqlbuilder in clickhouse store where possible

* refactor: move waterfall traverse logic to types

and extract out auto expanded span calculation

* chore: convert all timestamp to nano for consitancy

* chore: rename waterfall response to gettableX format

* chore: fix method calls in test after refactoring

* refactor: remove unused methods

* chore: fix openapi spec

* chore: better names for methods and vars

* chore: remove caching to match from v2

* chore: update openapi client

* refactor: move selection decision to types

* chore: move types to the top

* refactor: avoid passing the whole telementry store in a module

* refactor: move waterfall constants to module config

* chore: update openapi specs

* chore: update openapi clints
2026-04-24 05:01:30 +00:00
SagarRajput-7
85a38d5608 feat(base-path): scope localStorage/sessionStorage keys to base path + fix livetail url (#11029)
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
Release Drafter / update_release_draft (push) Has been cancelled
* feat: base path config setup and plugin for gotmpl generation at build time

* feat: changed output path to dir level

* feat: refactor the interceptor and added gotmpl into gitignore

* feat: removed plugin and serving the index.html only as the template

* feat: updated the html template

* feat: updated base path utils and fixed navigation and translations

* feat: code refactor around feedbacks

* feat: applied suggested patch changes

* feat: code refactor around feedbacks

* feat(base-path): mirgate rule to oxlint

* feat(base-path): fix lint issues

* feat(base-path): replace window.open with openInNewTab for internal paths

* feat(base-path): migrate remaining pattern for window.location.origin + path

* feat(base-path): configure local dev setup

* feat(base-path): migrate backend bound urls and eslint upgrade to error (#11028)

* feat(base-path): migrate backend bound urls and eslint upgrade to error

* feat(base-path): migrated the new files added after rebase with main

* feat(base-path): updated lint error comment to oxlint

* feat(base-path): getScopedKey - scope storage keys to base path

* feat(base-path): scope localStorage wrapper keys via getScopedKey

* feat(base-path): sessionStorage wrappers + scope useLocalStorage via wrappers

* feat(base-path): route direct localStorage calls through scoped wrappers

* feat(base-path): route direct sessionStorage calls through scoped wrappers

* feat(base-path): eslint rule, ban direct localStorage/sessionStorage access

* fix(base-path): prepend withBasePath to livetail SSE URL in dev mode

* fix(base-path): rename loadModule to loadStorageModule to avoid TS global scope collision

* feat(base-path): migrated more cases

* feat(base-path): fix lint issues

* feat(base-path): added rule to oxlint and added oxlint disables

* feat(base-path): added localstorage fallback scope

* feat(base-path): replace window.open with openInNewTab for internal paths

* feat(base-path): migrate remaining pattern for window.location.origin + path

* feat(base-path): migrate backend bound urls and eslint upgrade to error (#11028)

* feat(base-path): migrate backend bound urls and eslint upgrade to error

* feat(base-path): migrated the new files added after rebase with main

* feat(base-path): updated lint error comment to oxlint
2026-04-23 19:17:34 +00:00
swapnil-signoz
04552fa2e9 feat: cloud integration azure service definitions (#11006)
* refactor: moving types to cloud provider specific namespace/pkg

* refactor: separating cloud provider types

* refactor: using upper case key for AWS

* feat: adding cloud integration azure types

* feat: adding azure services

* refactor: updating omitempty tags

* refactor: updating azure integration config

* feat: completing azure types

* refactor: lint issues

* feat: adding service definitions for azure

* refactor: update service names for Azure Blob Storage telemetry

* refactor: updating definitions with metrics and strategy

* refactor: updating command key

* fix: handle optional connection URL in AWS integration

* refactor: updating strategy struct

* refactor: updating telemetry strategy

* refactor: updating blob storage service name

* refactor: updating azure blob storage service name

* refactor: update Azure service identifiers

* refactor: updating service defs

* refactor: updating types
2026-04-23 18:42:10 +00:00
SagarRajput-7
535ee9900c feat(base-path): replace window.open with openInNewTab for internal paths and upgraded lint to error (#11027)
* feat(base-path): replace window.open with openInNewTab for internal paths

* feat(base-path): migrate remaining pattern for window.location.origin + path

* feat(base-path): migrate backend bound urls and eslint upgrade to error (#11028)

* feat(base-path): migrate backend bound urls and eslint upgrade to error

* feat(base-path): migrated the new files added after rebase with main

* feat(base-path): updated lint error comment to oxlint
2026-04-23 18:28:48 +00:00
Vikrant Gupta
07e7fcac4b feat(authz): add check API for community build (#11056)
* feat(authz): add check API for community build

* feat(authz): move to types

* feat(authz): fix the role corelations

* feat(authz): fix the role corelations

* fix(authz): single line returns
2026-04-23 17:59:46 +00:00
SagarRajput-7
c595506a09 feat: base path config setup and index.html setup as go template for BE injection (#11026)
* feat: base path config setup and plugin for gotmpl generation at build time

* feat: changed output path to dir level

* feat: refactor the interceptor and added gotmpl into gitignore

* feat: removed plugin and serving the index.html only as the template

* feat: updated the html template

* feat: updated base path utils and fixed navigation and translations

* feat: code refactor around feedbacks

* feat: applied suggested patch changes

* feat: code refactor around feedbacks

* feat(base-path): mirgate rule to oxlint

* feat(base-path): fix lint issues

* feat(base-path): configure local dev setup
2026-04-23 17:08:00 +00:00
swapnil-signoz
21ce7a663a feat: adding cloud integration azure types and openapi spec (#11005)
* refactor: moving types to cloud provider specific namespace/pkg

* refactor: separating cloud provider types

* refactor: using upper case key for AWS

* feat: adding cloud integration azure types

* feat: adding azure services

* refactor: updating omitempty tags

* refactor: updating azure integration config

* feat: completing azure types

* refactor: lint issues

* refactor: updating command key

* fix: handle optional connection URL in AWS integration

* refactor: updating strategy struct

* refactor: updating azure blob storage service name

* refactor: update Azure service identifiers

* refactor: updating types
2026-04-23 14:48:04 +00:00
Abhi kumar
e6e2f95ec2 fix: minor fixes in tooltip ui (#11077)
* fix: minor fixes in tooltip ui

* chore: minor changes

* chore: minor fixes
2026-04-23 13:32:57 +00:00
Vikrant Gupta
afe85c48f9 feat(authz): add support for delete role (#11044)
* feat(authz): add support for delete role

* feat(authz): register config and return error on cleanup failure

* feat(authz): take user and serviceaccount DI for assignee checks

* feat(authz): add the example yaml

* feat(authz): move to callbacks instead of DI
2026-04-23 13:25:19 +00:00
Pandey
aeadeacc70 chore(tests): bump deps to close 8 dependabot alerts (#11076)
Bumps direct pins pytest>=9.0.3 (GHSA-6w46-j5rx-g56g) and requests>=2.33.0
(GHSA-gc5v-m9x4-r6x2). uv lock --upgrade then refreshes everything
transitive, which covers:

- cryptography 46.0.3 -> 46.0.7 (GHSA-r6ph-v2qm-q3c2 high, GHSA-p423-j2cm-9vmq
  medium, GHSA-m959-cc7f-wv43 low)
- python-dotenv 1.2.1 -> 1.2.2 (GHSA-mf9w-mj56-hr94)
- Pygments 2.19.2 -> 2.20.0 (GHSA-5239-wwwm-4pmq)
- jwcrypto 1.5.6 -> 1.5.7 (GHSA-fjrm-76x2-c4q4 — PyPI has 1.5.7, GitHub's
  advisory hasn't catalogued the patched version yet)

Risk: python-keycloak majored 6.0.0 -> 7.1.1. The 7.0 release tightens
return-type handling and can now raise TypeError on mismatch. Imports
collect cleanly (499 tests) but only the callbackauthn suite exercises
KeycloakAdmin at runtime — watch that job in CI.
2026-04-23 12:56:29 +00:00
Vikrant Gupta
6996d41b01 fix(serviceaccount): status code for deleted service accounts (#11075)
* fix(serviceaccount): status code for deleted service accounts

* fix(authz): plural relation endpoints
2026-04-23 12:53:52 +00:00
Pandey
f62024ad3f chore: modern fmts and lints for tests/ (#11074)
* chore(frontend): remove stale e2e scaffold

frontend/e2e/ held an unused settings-only test-plan scaffold from Oct 2025.
Active Playwright specs live at tests/e2e/. Drop the directory, the orphan
playwright.config.ts, the @playwright/test dependency, and the tsconfig
references that pinned them.

* chore(e2e): migrate formatter from prettier to oxfmt

Swap tests/e2e/ onto oxfmt — same tool the frontend adopted in #11057. Style
matches frontend/.oxfmtrc.json (tabs, tabWidth:1) so the two TS trees stay
visually consistent. Drops .prettierrc.json and .prettierignore, adds the
fmt/fmt:check yarn scripts, and reformats the existing specs.

* chore(e2e): migrate linter from eslint to oxlint

Drop eslint + @typescript-eslint plugins in favour of oxlint 1.59 + tsgolint
— same toolchain the frontend adopted in #10176. The .oxlintrc.json mirrors
frontend/.oxlintrc.json with plugins scoped to a Playwright TS codebase
(eslint, typescript, unicorn, import, promise).

Divergence: eslint-plugin-playwright is not ported. Its rules depend on
ESLint APIs (context.getAncestors) that oxlint's JS plugin shim does not
implement, so the five playwright/* rules are dropped in this migration.

* ci(e2e): add fmtlint job

Mirror integrationci.yaml's fmtlint job for e2e. Runs oxfmt --check and
oxlint on tests/e2e/ under the same safe-to-e2e label gating as the
existing test job.

* chore(integration): migrate python tooling from black/pylint/isort/autoflake to ruff

Replace the four-tool stack with ruff — same motivation as the oxfmt/oxlint
swap on the TS side. One tool covers formatting (ruff format), import
sorting (I), unused-import/variable cleanup (F401/F841), and the pylint
rules we actually care about (E/W/F/UP/B/PL).

Rule set mirrors the intent of the prior pylint config: too-many-* checks
and magic-value-comparison stay disabled, dangerous-default-value (now
B006) stays muted. A handful of newly-surfaced codes (B011/B024/B905/E741/
UP047/PLC0206/PLW2901) are also muted to keep this a pure tool swap — each
deserves its own review before enabling.

Divergence: ruff caps line-length at 320, so the prior pylint value of 400
drops to 320. Nothing in tree exceeds 320, so no lines wrap.

No changes to integrationci.yaml — both fmt/lint steps still call
make py-fmt / make py-lint, which now dispatch to ruff.

* chore(e2e): restore playwright lint rules via oxlint jsPlugin

eslint-plugin-playwright@2.x was rewritten against ESLint 8's
context.sourceCode.getAncestors() API, which oxlint's JS plugin shim
exposes. The 0.16.x version previously ruled out by context.getAncestors()
missing is no longer a blocker. Bump to 2.10.2, re-add it as a jsPlugin,
and restore the five rules dropped in the initial oxlint migration:
expect-expect, no-conditional-in-test, no-page-pause, no-wait-for-timeout,
prefer-web-first-assertions.

Rule count: 104 → 109.

* chore(frontend): remove stale e2e prompt

frontend/prompts/generate-e2e-test.md is leftover from the same Oct 2025
scaffold removed in ebf735dcc. It references frontend/e2e/utils/login.util.ts,
which no longer exists, and is not wired into anything.

* chore(e2e): make .env.local write layout explicit

The single f-string with inline \n escapes read as a wall of text after
ruff's line-length allowance collapsed it onto one line. Switch to a
triple-quoted f-string so the generated .env.local structure is visible
in source. Byte-for-byte identical output.

* chore(e2e): write .env.local one key per line

Open the file with a context manager and emit each key with its own
f.write call. Same output as before, but each key-value pair is a
discrete statement.
2026-04-23 12:01:42 +00:00
Naman Verma
eb9eb317cc test: perf test script for both sql flavours 2026-04-23 17:14:33 +05:30
Naman Verma
0b1eb16a42 test: fixes in dashboard perf testing data generator 2026-04-23 15:42:58 +05:30
Naman Verma
05a4d12183 test: script to generate test dashboard data in a sql db 2026-04-23 14:19:58 +05:30
Naman Verma
bbaf64c4f0 feat: openapi spec generation 2026-04-21 13:41:06 +05:30
1451 changed files with 41584 additions and 27688 deletions

56
.github/CODEOWNERS vendored
View File

@@ -52,49 +52,49 @@ go.mod @therealpandey
# Querier Owners
/pkg/querier/ @srikanthccv
/pkg/variables/ @srikanthccv
/pkg/types/querybuildertypes/ @srikanthccv
/pkg/types/telemetrytypes/ @srikanthccv
/pkg/querybuilder/ @srikanthccv
/pkg/telemetrylogs/ @srikanthccv
/pkg/telemetrymetadata/ @srikanthccv
/pkg/telemetrymetrics/ @srikanthccv
/pkg/telemetrytraces/ @srikanthccv
/pkg/querier/ @srikanthccv @therealpandey
/pkg/variables/ @srikanthccv @therealpandey
/pkg/types/querybuildertypes/ @srikanthccv @therealpandey
/pkg/types/telemetrytypes/ @srikanthccv @therealpandey
/pkg/querybuilder/ @srikanthccv @therealpandey
/pkg/telemetrylogs/ @srikanthccv @therealpandey
/pkg/telemetrymetadata/ @srikanthccv @therealpandey
/pkg/telemetrymetrics/ @srikanthccv @therealpandey
/pkg/telemetrytraces/ @srikanthccv @therealpandey
# Metrics
/pkg/types/metrictypes/ @srikanthccv
/pkg/types/metricsexplorertypes/ @srikanthccv
/pkg/modules/metricsexplorer/ @srikanthccv
/pkg/prometheus/ @srikanthccv
/pkg/types/metrictypes/ @srikanthccv @therealpandey
/pkg/types/metricsexplorertypes/ @srikanthccv @therealpandey
/pkg/modules/metricsexplorer/ @srikanthccv @therealpandey
/pkg/prometheus/ @srikanthccv @therealpandey
# APM
/pkg/types/servicetypes/ @srikanthccv
/pkg/types/apdextypes/ @srikanthccv
/pkg/modules/apdex/ @srikanthccv
/pkg/modules/services/ @srikanthccv
/pkg/types/servicetypes/ @srikanthccv @therealpandey
/pkg/types/apdextypes/ @srikanthccv @therealpandey
/pkg/modules/apdex/ @srikanthccv @therealpandey
/pkg/modules/services/ @srikanthccv @therealpandey
# Dashboard
/pkg/types/dashboardtypes/ @srikanthccv
/pkg/modules/dashboard/ @srikanthccv
/pkg/types/dashboardtypes/ @srikanthccv @therealpandey
/pkg/modules/dashboard/ @srikanthccv @therealpandey
# Rule/Alertmanager
/pkg/types/ruletypes/ @srikanthccv
/pkg/types/alertmanagertypes @srikanthccv
/pkg/alertmanager/ @srikanthccv
/pkg/ruler/ @srikanthccv
/pkg/modules/rulestatehistory/ @srikanthccv
/pkg/types/rulestatehistorytypes/ @srikanthccv
/pkg/types/ruletypes/ @srikanthccv @therealpandey
/pkg/types/alertmanagertypes @srikanthccv @therealpandey
/pkg/alertmanager/ @srikanthccv @therealpandey
/pkg/ruler/ @srikanthccv @therealpandey
/pkg/modules/rulestatehistory/ @srikanthccv @therealpandey
/pkg/types/rulestatehistorytypes/ @srikanthccv @therealpandey
# Correlation-adjacent
/pkg/contextlinks/ @srikanthccv
/pkg/types/parsertypes/ @srikanthccv
/pkg/queryparser/ @srikanthccv
/pkg/contextlinks/ @srikanthccv @therealpandey
/pkg/types/parsertypes/ @srikanthccv @therealpandey
/pkg/queryparser/ @srikanthccv @therealpandey
# AuthN / AuthZ Owners

View File

@@ -9,6 +9,27 @@ on:
- labeled
jobs:
fmtlint:
if: |
((github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && contains(github.event.pull_request.labels.*.name, 'safe-to-e2e')
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: install
run: |
cd tests/e2e && yarn install --frozen-lockfile
- name: fmt
run: |
cd tests/e2e && yarn fmt:check
- name: lint
run: |
cd tests/e2e && yarn lint
test:
strategy:
fail-fast: false

View File

@@ -51,6 +51,7 @@ jobs:
- role
- rootuser
- serviceaccount
- querier_json_body
- ttl
sqlstore-provider:
- postgres
@@ -61,7 +62,7 @@ jobs:
- 25.5.6
- 25.12.5
schema-migrator-version:
- v0.142.0
- v0.144.3
postgres-version:
- 15
if: |

View File

@@ -1,6 +1,8 @@
{
"oxc.typeAware": true,
"oxc.tsConfigPath": "./frontend/tsconfig.json",
"oxc.configPath": "./frontend/.oxlintrc.json",
"oxc.fmt.configPath": "./frontend/.oxfmtrc.json",
"editor.formatOnSave": true,
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.codeActionsOnSave": {
@@ -19,4 +21,3 @@
"python-envs.defaultEnvManager": "ms-python.python:system",
"python-envs.pythonProjects": []
}

View File

@@ -201,14 +201,12 @@ docker-buildx-enterprise: go-build-enterprise js-build
# python commands
##############################################################
.PHONY: py-fmt
py-fmt: ## Run black across the shared tests project
@cd tests && uv run black .
py-fmt: ## Run ruff format across the shared tests project
@cd tests && uv run ruff format .
.PHONY: py-lint
py-lint: ## Run lint across the shared tests project
@cd tests && uv run isort .
@cd tests && uv run autoflake .
@cd tests && uv run pylint .
py-lint: ## Run ruff check across the shared tests project
@cd tests && uv run ruff check --fix .
.PHONY: py-test-setup
py-test-setup: ## Bring up the shared SigNoz backend used by integration and e2e tests

View File

@@ -27,6 +27,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/tag"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
@@ -92,7 +93,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
},
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ []authz.OnBeforeRoleDelete, _ dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore)
if err != nil {
return nil, err
@@ -100,8 +101,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore), nil
},
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser)
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing, tagModule tag.Module) dashboard.Module {
return impldashboard.NewModule(impldashboard.NewStore(store), store, settings, analytics, orgGetter, queryParser, tagModule)
},
func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
return noopgateway.NewProviderFactory()

View File

@@ -42,6 +42,7 @@ import (
pkgcloudintegration "github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/tag"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
@@ -137,15 +138,15 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return authNs, nil
},
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, dashboardModule dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, dashboardModule dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore)
if err != nil {
return nil, err
}
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, dashboardModule), nil
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, onBeforeRoleDelete, dashboardModule), nil
},
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing)
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing, tagModule tag.Module) dashboard.Module {
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), store, settings, analytics, orgGetter, queryParser, querier, licensing, tagModule)
},
func(licensing licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
return httpgateway.NewProviderFactory(licensing)
@@ -167,7 +168,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
if err != nil {
return nil, err
}
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider()
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider(defStore)
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,

View File

@@ -11,6 +11,8 @@ global:
external_url: <unset>
# the url where the SigNoz backend receives telemetry data (traces, metrics, logs) from instrumented applications.
ingestion_url: <unset>
# the url of the SigNoz MCP server. when unset, the MCP settings page is hidden in the frontend.
# mcp_url: <unset>
##################### Version #####################
version:
@@ -407,3 +409,21 @@ cloudintegration:
agent:
# The version of the cloud integration agent.
version: v0.0.8
##################### Trace Detail #####################
tracedetail:
waterfall:
# Number of spans returned per request when the trace is too large to show all at once.
span_page_size: 500
# Maximum depth of descendents to auto-expand for the selected span.
max_depth_to_auto_expand: 5
# Threshold below which all spans are returned without windowing.
max_limit_to_select_all_spans: 10000
##################### Authz #################################
authz:
# Specifies the authz provider to use.
provider: openfga
openfga:
# maximum tuples allowed per openfga write operation.
max_tuples_per_write: 100

File diff suppressed because it is too large Load Diff

View File

@@ -20,20 +20,23 @@ import (
)
type provider struct {
pkgAuthzService authz.AuthZ
openfgaServer *openfgaserver.Server
licensing licensing.Licensing
store authtypes.RoleStore
registry []authz.RegisterTypeable
config authz.Config
pkgAuthzService authz.AuthZ
openfgaServer *openfgaserver.Server
licensing licensing.Licensing
store authtypes.RoleStore
registry []authz.RegisterTypeable
settings factory.ScopedProviderSettings
onBeforeRoleDelete []authz.OnBeforeRoleDelete
}
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] {
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] {
return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) {
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore, licensing, registry)
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore, licensing, onBeforeRoleDelete, registry)
})
}
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema, openfgaDataStore)
pkgAuthzService, err := pkgOpenfgaAuthzProvider.New(ctx, settings, config)
if err != nil {
@@ -45,12 +48,17 @@ func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings,
return nil, err
}
scopedSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/ee/authz/openfgaauthz")
return &provider{
pkgAuthzService: pkgAuthzService,
openfgaServer: openfgaServer,
licensing: licensing,
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
registry: registry,
config: config,
pkgAuthzService: pkgAuthzService,
openfgaServer: openfgaServer,
licensing: licensing,
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
registry: registry,
settings: scopedSettings,
onBeforeRoleDelete: onBeforeRoleDelete,
}, nil
}
@@ -78,14 +86,40 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq map[string]*o
return provider.openfgaServer.BatchCheck(ctx, tupleReq)
}
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
return provider.openfgaServer.ListObjects(ctx, subject, relation, typeable)
func (provider *provider) CheckTransactions(ctx context.Context, subject string, orgID valuer.UUID, transactions []*authtypes.Transaction) ([]*authtypes.TransactionWithAuthorization, error) {
tuples, err := authtypes.NewTuplesFromTransactions(transactions, subject, orgID)
if err != nil {
return nil, err
}
batchResults, err := provider.openfgaServer.BatchCheck(ctx, tuples)
if err != nil {
return nil, err
}
results := make([]*authtypes.TransactionWithAuthorization, len(transactions))
for i, txn := range transactions {
result := batchResults[txn.ID.StringValue()]
results[i] = &authtypes.TransactionWithAuthorization{
Transaction: txn,
Authorized: result.Authorized,
}
}
return results, nil
}
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
return provider.openfgaServer.ListObjects(ctx, subject, relation, objectType)
}
func (provider *provider) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error {
return provider.openfgaServer.Write(ctx, additions, deletions)
}
func (provider *provider) ReadTuples(ctx context.Context, tupleKey *openfgav1.ReadRequestTupleKey) ([]*openfgav1.TupleKey, error) {
return provider.openfgaServer.ReadTuples(ctx, tupleKey)
}
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.Role, error) {
return provider.pkgAuthzService.Get(ctx, orgID, id)
}
@@ -146,7 +180,7 @@ func (provider *provider) Create(ctx context.Context, orgID valuer.UUID, role *a
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
return provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
return provider.store.Create(ctx, role)
}
func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, role *authtypes.Role) (*authtypes.Role, error) {
@@ -163,10 +197,10 @@ func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, ro
}
if existingRole != nil {
return authtypes.NewRoleFromStorableRole(existingRole), nil
return existingRole, nil
}
err = provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
err = provider.store.Create(ctx, role)
if err != nil {
return nil, err
}
@@ -175,14 +209,13 @@ func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, ro
}
func (provider *provider) GetResources(_ context.Context) []*authtypes.Resource {
typeables := make([]authtypes.Typeable, 0)
for _, register := range provider.registry {
typeables = append(typeables, register.MustGetTypeables()...)
}
typeables = append(typeables, provider.MustGetTypeables()...)
resources := make([]*authtypes.Resource, 0)
for _, typeable := range typeables {
for _, register := range provider.registry {
for _, typeable := range register.MustGetTypeables() {
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
}
}
for _, typeable := range provider.MustGetTypeables() {
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
}
@@ -201,21 +234,23 @@ func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id
}
objects := make([]*authtypes.Object, 0)
for _, resource := range provider.GetResources(ctx) {
if slices.Contains(authtypes.TypeableRelations[resource.Type], relation) {
resourceObjects, err := provider.
ListObjects(
ctx,
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.Name, orgID, &authtypes.RelationAssignee),
relation,
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
)
if err != nil {
return nil, err
}
objects = append(objects, resourceObjects...)
for _, objectType := range provider.getUniqueTypes() {
if !slices.Contains(authtypes.TypeableRelations[objectType], relation) {
continue
}
resourceObjects, err := provider.
ListObjects(
ctx,
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.Name, orgID, &authtypes.RelationAssignee),
relation,
objectType,
)
if err != nil {
return nil, err
}
objects = append(objects, resourceObjects...)
}
return objects, nil
@@ -227,7 +262,7 @@ func (provider *provider) Patch(ctx context.Context, orgID valuer.UUID, role *au
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
return provider.store.Update(ctx, orgID, authtypes.NewStorableRoleFromRole(role))
return provider.store.Update(ctx, orgID, role)
}
func (provider *provider) PatchObjects(ctx context.Context, orgID valuer.UUID, name string, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
@@ -260,17 +295,26 @@ func (provider *provider) Delete(ctx context.Context, orgID valuer.UUID, id valu
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
storableRole, err := provider.store.Get(ctx, orgID, id)
role, err := provider.store.Get(ctx, orgID, id)
if err != nil {
return err
}
role := authtypes.NewRoleFromStorableRole(storableRole)
err = role.ErrIfManaged()
if err != nil {
return err
}
for _, cb := range provider.onBeforeRoleDelete {
if err := cb(ctx, orgID, id); err != nil {
return err
}
}
if err := provider.deleteTuples(ctx, role.Name, orgID); err != nil {
return errors.WithAdditionalf(err, "failed to delete tuples for the role: %s", role.Name)
}
return provider.store.Delete(ctx, orgID, id)
}
@@ -346,3 +390,62 @@ func (provider *provider) getManagedRoleTransactionTuples(orgID valuer.UUID) ([]
return tuples, nil
}
func (provider *provider) deleteTuples(ctx context.Context, roleName string, orgID valuer.UUID) error {
subject := authtypes.MustNewSubject(authtypes.TypeableRole, roleName, orgID, &authtypes.RelationAssignee)
tuples := make([]*openfgav1.TupleKey, 0)
for _, objectType := range provider.getUniqueTypes() {
typeTuples, err := provider.ReadTuples(ctx, &openfgav1.ReadRequestTupleKey{
User: subject,
Object: objectType.StringValue() + ":",
})
if err != nil {
return err
}
tuples = append(tuples, typeTuples...)
}
if len(tuples) == 0 {
return nil
}
for idx := 0; idx < len(tuples); idx += provider.config.OpenFGA.MaxTuplesPerWrite {
end := idx + provider.config.OpenFGA.MaxTuplesPerWrite
if end > len(tuples) {
end = len(tuples)
}
err := provider.Write(ctx, nil, tuples[idx:end])
if err != nil {
return err
}
}
return nil
}
func (provider *provider) getUniqueTypes() []authtypes.Type {
seen := make(map[string]struct{})
uniqueTypes := make([]authtypes.Type, 0)
for _, register := range provider.registry {
for _, typeable := range register.MustGetTypeables() {
typeKey := typeable.Type().StringValue()
if _, ok := seen[typeKey]; ok {
continue
}
seen[typeKey] = struct{}{}
uniqueTypes = append(uniqueTypes, typeable.Type())
}
}
for _, typeable := range provider.MustGetTypeables() {
typeKey := typeable.Type().StringValue()
if _, ok := seen[typeKey]; ok {
continue
}
seen[typeKey] = struct{}{}
uniqueTypes = append(uniqueTypes, typeable.Type())
}
return uniqueTypes
}

View File

@@ -110,10 +110,14 @@ func (server *Server) BatchCheck(ctx context.Context, tupleReq map[string]*openf
return server.pkgAuthzService.BatchCheck(ctx, tupleReq)
}
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
return server.pkgAuthzService.ListObjects(ctx, subject, relation, typeable)
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
return server.pkgAuthzService.ListObjects(ctx, subject, relation, objectType)
}
func (server *Server) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error {
return server.pkgAuthzService.Write(ctx, additions, deletions)
}
func (server *Server) ReadTuples(ctx context.Context, tupleKey *openfgav1.ReadRequestTupleKey) ([]*openfgav1.TupleKey, error) {
return server.pkgAuthzService.ReadTuples(ctx, tupleKey)
}

View File

@@ -18,6 +18,7 @@ func NewAWSCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore)
return &awscloudprovider{serviceDefinitions: defStore}, nil
}
// TODO: move URL construction logic to cloudintegrationtypes and add unit tests for it.
func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.AWS.DeploymentRegion)
u, _ := url.Parse(baseURL)

View File

@@ -2,27 +2,48 @@ package implcloudprovider
import (
"context"
"sort"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type azurecloudprovider struct{}
type azurecloudprovider struct {
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
}
func NewAzureCloudProvider() cloudintegration.CloudProviderModule {
return &azurecloudprovider{}
func NewAzureCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) cloudintegration.CloudProviderModule {
return &azurecloudprovider{
serviceDefinitions: defStore,
}
}
func (provider *azurecloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
panic("implement me")
connectionArtifact, err := cloudintegrationtypes.NewAzureConnectionArtifact(account.ID, req.Config.AgentVersion, req.Credentials, req.Config.Azure)
if err != nil {
return nil, err
}
return &cloudintegrationtypes.ConnectionArtifact{
Azure: connectionArtifact,
}, nil
}
func (provider *azurecloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
return provider.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeAzure)
}
func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
serviceDef, err := provider.serviceDefinitions.Get(ctx, cloudintegrationtypes.CloudProviderTypeAzure, serviceID)
if err != nil {
return nil, err
}
// override cloud integration dashboard id.
for index, dashboard := range serviceDef.Assets.Dashboards {
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAzure, serviceID.StringValue(), dashboard.ID)
}
return serviceDef, nil
}
func (provider *azurecloudprovider) BuildIntegrationConfig(
@@ -30,5 +51,56 @@ func (provider *azurecloudprovider) BuildIntegrationConfig(
account *cloudintegrationtypes.Account,
services []*cloudintegrationtypes.StorableCloudIntegrationService,
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
panic("implement me")
sort.Slice(services, func(i, j int) bool {
return services[i].Type.StringValue() < services[j].Type.StringValue()
})
var strategies []*cloudintegrationtypes.AzureTelemetryCollectionStrategy
for _, storedSvc := range services {
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(cloudintegrationtypes.CloudProviderTypeAzure, storedSvc.Config)
if err != nil {
return nil, err
}
svcDef, err := provider.GetServiceDefinition(ctx, storedSvc.Type)
if err != nil {
return nil, err
}
strategy := svcDef.TelemetryCollectionStrategy.Azure
if strategy == nil {
continue
}
logsEnabled := svcCfg.IsLogsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
metricsEnabled := svcCfg.IsMetricsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
if !logsEnabled && !metricsEnabled {
continue
}
entry := &cloudintegrationtypes.AzureTelemetryCollectionStrategy{
ResourceProvider: strategy.ResourceProvider,
ResourceType: strategy.ResourceType,
}
if metricsEnabled && strategy.Metrics != nil {
entry.Metrics = strategy.Metrics
}
if logsEnabled && strategy.Logs != nil {
entry.Logs = strategy.Logs
}
strategies = append(strategies, entry)
}
return &cloudintegrationtypes.ProviderIntegrationConfig{
Azure: cloudintegrationtypes.NewAzureIntegrationConfig(
account.Config.Azure.DeploymentRegion,
account.Config.Azure.ResourceGroups,
strategies,
),
}, nil
}

View File

@@ -429,9 +429,13 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
stats["cloudintegration.aws.connectedaccounts.count"] = awsAccountsCount
}
// NOTE: not adding stats for services for now.
// get connected accounts for Azure
azureAccountsCount, err := module.store.CountConnectedAccounts(ctx, orgID, cloudintegrationtypes.CloudProviderTypeAzure)
if err == nil {
stats["cloudintegration.azure.connectedaccounts.count"] = azureAccountsCount
}
// TODO: add more cloud providers when supported
// NOTE: not adding stats for services for now.
return stats, nil
}

View File

@@ -11,12 +11,15 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/tag"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes/dashboardtypesv2"
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -30,9 +33,9 @@ type module struct {
licensing licensing.Licensing
}
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
func NewModule(store dashboardtypes.Store, sqlstore sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing, tagModule tag.Module) dashboard.Module {
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard")
pkgDashboardModule := pkgimpldashboard.NewModule(store, settings, analytics, orgGetter, queryParser)
pkgDashboardModule := pkgimpldashboard.NewModule(store, sqlstore, settings, analytics, orgGetter, queryParser, tagModule)
return &module{
pkgDashboardModule: pkgDashboardModule,
@@ -197,6 +200,18 @@ func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy s
return module.pkgDashboardModule.Create(ctx, orgID, createdBy, creator, data)
}
func (module *module) CreateV2(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, postable dashboardtypesv2.PostableDashboard) (*dashboardtypesv2.Dashboard, error) {
return module.pkgDashboardModule.CreateV2(ctx, orgID, createdBy, creator, postable)
}
func (module *module) GetV2(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypesv2.Dashboard, error) {
return module.pkgDashboardModule.GetV2(ctx, orgID, id)
}
func (module *module) UpdateV2(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, updateable dashboardtypesv2.UpdateableDashboard) (*dashboardtypesv2.Dashboard, error) {
return module.pkgDashboardModule.UpdateV2(ctx, orgID, id, updatedBy, updateable)
}
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
return module.pkgDashboardModule.Get(ctx, orgID, id)
}

View File

@@ -71,6 +71,15 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
Route: "",
})
bodyJSONQuery := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, evalCtx)
featureSet = append(featureSet, &licensetypes.Feature{
Name: valuer.NewString(flagger.FeatureUseJSONBody.String()),
Active: bodyJSONQuery,
Usage: 0,
UsageLimit: -1,
Route: "",
})
if constants.IsDotMetricsEnabled {
for idx, feature := range featureSet {
if feature.Name == licensetypes.DotMetricsEnabled {

View File

@@ -12,7 +12,6 @@ import (
"github.com/SigNoz/signoz/pkg/cache/memorycache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule/impllmpricingrule"
"github.com/gorilla/handlers"
@@ -106,17 +105,16 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
signoz.SQLStore,
integrationsController.GetPipelinesForInstalledIntegrations,
reader,
signoz.Flagger,
)
if err != nil {
return nil, err
}
// initiate agent config handler
llmCostFeature := impllmpricingrule.NewLLMCostFeature(signoz.Modules.LLMPricingRule)
agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{
Store: signoz.SQLStore,
AgentFeatures: []agentConf.AgentFeature{logParsingPipelineController, llmCostFeature},
AgentFeatures: []agentConf.AgentFeature{logParsingPipelineController},
})
if err != nil {
return nil, err

View File

@@ -9,7 +9,6 @@
"react",
"react-perf",
"typescript",
"unicorn",
"jsx-a11y",
"import",
"jest",
@@ -206,6 +205,8 @@
"@typescript-eslint/explicit-function-return-type": "error",
// Requires explicit return types on functions
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/no-useless-default-assignment": "off", // provide unsafe fixes in our codebase due to bad typing
"@typescript-eslint/no-duplicate-type-constituents": "off", // provide fixes that breaks some assumptions, eg: type A = L, B = L, C = A | B (removes B)
// Disallows require() in TypeScript (use import instead)
// Disabled - using TypeScript instead
"react/jsx-props-no-spreading": "off",
@@ -286,6 +287,20 @@
// Prevents useStore.getState() - export standalone actions instead
"signoz/no-navigator-clipboard": "error",
// Prevents navigator.clipboard - use useCopyToClipboard hook instead (disabled in tests via override)
"signoz/no-raw-absolute-path": "error",
// Prevents window.open(path), window.location.origin + path, window.location.href = path
"no-restricted-globals": [
"error",
{
"name": "localStorage",
"message": "Use scoped wrappers from api/browser/localstorage/ instead (ensures keys are prefixed when served under a URL base path)."
},
{
"name": "sessionStorage",
"message": "Use scoped wrappers from api/browser/sessionstorage/ instead (ensures keys are prefixed when served under a URL base path)."
}
],
// Prevents direct localStorage/sessionStorage access — use scoped wrappers
"no-restricted-imports": [
"error",
{
@@ -298,14 +313,6 @@
"name": "react-redux",
"message": "[State mgmt] react-redux is deprecated. Migrate to Zustand, nuqs, or react-query."
},
{
"name": "xstate",
"message": "[State mgmt] xstate is deprecated. Migrate to Zustand or react-query."
},
{
"name": "@xstate/react",
"message": "[State mgmt] @xstate/react is deprecated. Migrate to Zustand or react-query."
},
{
"name": "react",
"importNames": [
@@ -324,82 +331,6 @@
"react/no-array-index-key": "warn",
// TODO: Changed to warn during oxlint migration, should be changed to error,
"unicorn/error-message": "warn",
"unicorn/escape-case": "warn",
"unicorn/new-for-builtins": "warn",
"unicorn/no-abusive-eslint-disable": "warn",
"unicorn/no-console-spaces": "warn",
"unicorn/no-instanceof-array": "warn",
"unicorn/no-invalid-remove-event-listener": "warn",
"unicorn/no-new-array": "warn",
"unicorn/no-new-buffer": "warn",
"unicorn/no-thenable": "warn",
"unicorn/no-unreadable-array-destructuring": "warn",
"unicorn/no-useless-fallback-in-spread": "warn",
"unicorn/no-useless-length-check": "warn",
"unicorn/no-useless-promise-resolve-reject": "warn",
"unicorn/no-useless-spread": "warn",
"unicorn/no-zero-fractions": "warn",
"unicorn/number-literal-case": "warn",
"unicorn/prefer-array-find": "warn",
"unicorn/prefer-array-flat": "warn",
"unicorn/prefer-array-flat-map": "warn",
"unicorn/prefer-array-index-of": "warn",
"unicorn/prefer-array-some": "warn",
"unicorn/prefer-at": "warn",
"unicorn/prefer-code-point": "warn",
"unicorn/prefer-date-now": "warn",
"unicorn/prefer-default-parameters": "warn",
"unicorn/prefer-includes": "warn",
"unicorn/prefer-modern-math-apis": "warn",
"unicorn/prefer-native-coercion-functions": "warn",
"unicorn/prefer-node-protocol": "off",
"unicorn/prefer-number-properties": "warn",
"unicorn/prefer-optional-catch-binding": "warn",
"unicorn/prefer-regexp-test": "warn",
"unicorn/prefer-set-has": "warn",
"unicorn/prefer-string-replace-all": "warn",
"unicorn/prefer-string-slice": "warn",
"unicorn/prefer-string-starts-ends-with": "warn",
"unicorn/prefer-string-trim-start-end": "warn",
"unicorn/prefer-type-error": "warn",
"unicorn/require-array-join-separator": "warn",
"unicorn/require-number-to-fixed-digits-argument": "warn",
"unicorn/throw-new-error": "warn",
"unicorn/consistent-function-scoping": "warn",
"unicorn/explicit-length-check": "warn",
"unicorn/filename-case": [
"warn",
{
"case": "kebabCase"
}
],
"unicorn/no-array-for-each": "warn",
"unicorn/no-lonely-if": "warn",
"unicorn/no-negated-condition": "warn",
"unicorn/no-null": "warn",
"unicorn/no-object-as-default-parameter": "warn",
"unicorn/no-static-only-class": "warn",
"unicorn/no-this-assignment": "warn",
"unicorn/no-unreadable-iife": "warn",
"unicorn/no-useless-switch-case": "warn",
"unicorn/no-useless-undefined": "warn",
"unicorn/prefer-add-event-listener": "warn",
"unicorn/prefer-dom-node-append": "warn",
"unicorn/prefer-dom-node-dataset": "warn",
"unicorn/prefer-dom-node-remove": "warn",
"unicorn/prefer-dom-node-text-content": "warn",
"unicorn/prefer-keyboard-event-key": "warn",
"unicorn/prefer-math-trunc": "warn",
"unicorn/prefer-modern-dom-apis": "warn",
"unicorn/prefer-negative-index": "warn",
"unicorn/prefer-prototype-methods": "warn",
"unicorn/prefer-query-selector": "warn",
"unicorn/prefer-reflect-apply": "warn",
"unicorn/prefer-set-size": "warn",
"unicorn/prefer-spread": "warn",
"unicorn/prefer-ternary": "warn",
"unicorn/require-post-message-target-origin": "warn",
"oxc/bad-array-method-on-arguments": "error",
"oxc/bad-bitwise-operator": "error",
"oxc/bad-comparison-sequence": "error",
@@ -524,8 +455,8 @@
"sonarjs/no-nested-template-literals": "error",
// Avoids nested template literals
"sonarjs/no-redundant-boolean": "warn", // TODO: Change to error after migration
// Removes redundant boolean literals
"sonarjs/no-redundant-jump": "error",
// Removes redundant boolean literals - turned off because it clashes with unicorn rules
"sonarjs/no-redundant-jump": "off",
// Removes unnecessary returns/continues
"sonarjs/no-same-line-conditional": "error",
// Prevents same-line conditionals
@@ -597,8 +528,12 @@
"rules": {
"import/first": "off",
// Should ignore due to mocks
"signoz/no-navigator-clipboard": "off"
// Tests can use navigator.clipboard directly
"signoz/no-navigator-clipboard": "off",
// Tests can use navigator.clipboard directly,
"signoz/no-raw-absolute-path":"off",
"no-restricted-globals": "off",
// Tests need raw localStorage/sessionStorage to seed DOM state for isolation
"signoz/no-zustand-getstate-in-hooks": "off"
}
},
{

View File

@@ -1,3 +1,4 @@
// oxlint-disable-next-line typescript/no-require-imports
const path = require('path');
module.exports = {

View File

@@ -1,29 +0,0 @@
# SigNoz E2E Test Plan
This directory contains the structured test plan for the SigNoz application. Each subfolder corresponds to a main module or feature area, and contains scenario files for all user journeys, edge cases, and cross-module flows. These documents serve as the basis for generating Playwright MCP-driven E2E tests.
## Structure
- Each main module (e.g., logs, traces, dashboards, alerts, settings, etc.) has its own folder or markdown file.
- Each file contains detailed scenario templates, including preconditions, step-by-step actions, and expected outcomes.
- Use these documents to write, review, and update test cases as the application evolves.
## Folders & Files
- `logs/` — Logs module scenarios
- `traces/` — Traces module scenarios
- `metrics/` — Metrics module scenarios
- `dashboards/` — Dashboards module scenarios
- `alerts/` — Alerts module scenarios
- `services/` — Services module scenarios
- `settings/` — Settings and all sub-settings scenarios
- `onboarding/` — Onboarding and signup flows
- `navigation/` — Navigation, sidebar, and cross-module flows
- `exceptions/` — Exception and error handling scenarios
- `external-apis/` — External API monitoring scenarios
- `messaging-queues/` — Messaging queue scenarios
- `infrastructure/` — Infrastructure monitoring scenarios
- `help-support/` — Help & support scenarios
- `user-preferences/` — User preferences and personalization scenarios
- `service-map/` — Service map scenarios
- `saved-views/` — Saved views scenarios

View File

@@ -1,16 +0,0 @@
# Settings Module Test Plan
This folder contains E2E test scenarios for the Settings module and all sub-settings.
## Scenario Categories
- General settings (org/workspace, branding, version info)
- Billing settings
- Members & SSO
- Custom domain
- Integrations
- Notification channels
- API keys
- Ingestion
- Account settings (profile, password, preferences)
- Keyboard shortcuts

View File

@@ -1,43 +0,0 @@
# Account Settings E2E Scenarios (Updated)
## 1. Update Name
- **Precondition:** User is logged in
- **Steps:**
1. Click 'Update name' button
2. Edit name field in the modal/dialog
3. Save changes
- **Expected:** Name is updated in the UI
## 2. Update Email
- **Note:** The email field is not editable in the current UI.
## 3. Reset Password
- **Precondition:** User is logged in
- **Steps:**
1. Click 'Reset password' button
2. Complete reset flow (modal/dialog or external flow)
- **Expected:** Password is reset
## 4. Toggle 'Adapt to my timezone'
- **Precondition:** User is logged in
- **Steps:**
1. Toggle 'Adapt to my timezone' switch
- **Expected:** Timezone adapts accordingly (UI feedback/confirmation should be checked)
## 5. Toggle Theme (Dark/Light)
- **Precondition:** User is logged in
- **Steps:**
1. Toggle theme radio buttons ('Dark', 'Light Beta')
- **Expected:** Theme changes
## 6. Toggle Sidebar Always Open
- **Precondition:** User is logged in
- **Steps:**
1. Toggle 'Keep the primary sidebar always open' switch
- **Expected:** Sidebar remains open/closed as per toggle

View File

@@ -1,26 +0,0 @@
# API Keys E2E Scenarios (Updated)
## 1. Create a New API Key
- **Precondition:** User is admin
- **Steps:**
1. Click 'New Key' button
2. Enter details in the modal/dialog
3. Click 'Save'
- **Expected:** API key is created and listed in the table
## 2. Revoke an API Key
- **Precondition:** API key exists
- **Steps:**
1. In the table, locate the API key row
2. Click the revoke/delete button (icon button in the Action column)
3. Confirm if prompted
- **Expected:** API key is revoked/removed from the table
## 3. View API Key Usage
- **Precondition:** API key exists
- **Steps:**
1. View the 'Last used' and 'Expired' columns in the table
- **Expected:** Usage data is displayed for each API key

View File

@@ -1,17 +0,0 @@
# Billing Settings E2E Scenarios (Updated)
## 1. View Billing Information
- **Precondition:** User is admin
- **Steps:**
1. Navigate to Billing Settings
2. Wait for the billing chart/data to finish loading
- **Expected:**
- Billing heading and subheading are displayed
- Usage/cost table is visible with columns: Unit, Data Ingested, Price per Unit, Cost (Billing period to date)
- "Download CSV" and "Manage Billing" buttons are present and enabled after loading
- Test clicking "Download CSV" and "Manage Billing" for expected behavior (e.g., file download, navigation, or modal)
> Note: If these features are expected to trigger specific flows, document the observed behavior for each button.

View File

@@ -1,18 +0,0 @@
# Custom Domain E2E Scenarios (Updated)
## 1. Add or Update Custom Domain
- **Precondition:** User is admin
- **Steps:**
1. Click 'Customize teams URL' button
2. In the 'Customize your teams URL' dialog, enter the preferred subdomain
3. Click 'Apply Changes'
- **Expected:** Domain is set/updated for the team (UI feedback/confirmation should be checked)
## 2. Verify Domain Ownership
- **Note:** No explicit 'Verify' button or flow is present in the current UI. If verification is required, it may be handled automatically or via support.
## 3. Remove a Custom Domain
- **Note:** No explicit 'Remove' button or flow is present in the current UI. The only available action is to update the subdomain.

View File

@@ -1,31 +0,0 @@
# General Settings E2E Scenarios
## 1. View General Settings
- **Precondition:** User is logged in
- **Steps:**
1. Navigate to General Settings
- **Expected:** General settings are displayed
## 2. Update Organization/Workspace Name
- **Precondition:** User is admin
- **Steps:**
1. Edit organization/workspace name
2. Save changes
- **Expected:** Name is updated and visible
## 3. Update Logo or Branding
- **Precondition:** User is admin
- **Steps:**
1. Upload new logo/branding
2. Save changes
- **Expected:** Branding is updated
## 4. View Version/Build Info
- **Precondition:** User is logged in
- **Steps:**
1. View version/build info section
- **Expected:** Version/build info is displayed

View File

@@ -1,20 +0,0 @@
# Ingestion E2E Scenarios (Updated)
## 1. View Ingestion Sources
- **Precondition:** User is admin
- **Steps:**
1. Navigate to the Integrations page
- **Expected:** List of available data sources/integrations is displayed
## 2. Configure Ingestion Sources
- **Precondition:** User is admin
- **Steps:**
1. Click 'Configure' for a data source/integration
2. Complete the configuration flow (modal or page, as available)
- **Expected:** Source is configured (UI feedback/confirmation should be checked)
## 3. Disable/Enable Ingestion
- **Note:** No visible enable/disable toggle for ingestion sources in the current UI. Ingestion is managed via the Integrations configuration flows.

View File

@@ -1,51 +0,0 @@
# Integrations E2E Scenarios (Updated)
## 1. View List of Available Integrations
- **Precondition:** User is logged in
- **Steps:**
1. Navigate to Integrations
- **Expected:** List of integrations is displayed, each with a name, description, and 'Configure' button
## 2. Search Integrations by Name/Type
- **Precondition:** Integrations exist
- **Steps:**
1. Enter search/filter criteria in the 'Search for an integration...' box
- **Expected:** Only matching integrations are shown
## 3. Connect a New Integration
- **Precondition:** User is admin
- **Steps:**
1. Click 'Configure' for an integration
2. Complete the configuration flow (modal or page, as available)
- **Expected:** Integration is connected/configured (UI feedback/confirmation should be checked)
## 4. Disconnect an Integration
- **Note:** No visible 'Disconnect' button in the main list. This may be available in the configuration flow for a connected integration.
## 5. Configure Integration Settings
- **Note:** Configuration is handled in the flow after clicking 'Configure' for an integration.
## 6. Test Integration Connection
- **Note:** No visible 'Test Connection' button in the main list. This may be available in the configuration flow.
## 7. View Integration Status/Logs
- **Note:** No visible status/logs section in the main list. This may be available in the configuration flow.
## 8. Filter Integrations by Category
- **Note:** No explicit category filter in the current UI, only a search box.
## 9. View Integration Documentation/Help
- **Note:** No visible 'Help/Docs' button in the main list. This may be available in the configuration flow.
## 10. Update Integration Configuration
- **Note:** Configuration is handled in the flow after clicking 'Configure' for an integration.

View File

@@ -1,19 +0,0 @@
# Keyboard Shortcuts E2E Scenarios (Updated)
## 1. View Keyboard Shortcuts
- **Precondition:** User is logged in
- **Steps:**
1. Navigate to Keyboard Shortcuts
- **Expected:** Shortcuts are displayed in categorized tables (Global, Logs Explorer, Query Builder, Dashboard)
## 2. Customize Keyboard Shortcuts (if supported)
- **Note:** Customization is not available in the current UI. Shortcuts are view-only.
## 3. Use Keyboard Shortcuts for Navigation/Actions
- **Precondition:** User is logged in
- **Steps:**
1. Use shortcut for navigation/action (e.g., shift+s for Services, cmd+enter for running query)
- **Expected:** Navigation/action is performed as per shortcut

View File

@@ -1,49 +0,0 @@
# Members & SSO E2E Scenarios (Updated)
## 1. Invite a New Member
- **Precondition:** User is admin
- **Steps:**
1. Click 'Invite Members' button
2. In the 'Invite team members' dialog, enter email address, name (optional), and select role
3. (Optional) Click 'Add another team member' to invite more
4. Click 'Invite team members' to send invite(s)
- **Expected:** Pending invite appears in the 'Pending Invites' table
## 2. Remove a Member
- **Precondition:** User is admin, member exists
- **Steps:**
1. In the 'Members' table, locate the member row
2. Click 'Delete' in the Action column
3. Confirm removal if prompted
- **Expected:** Member is removed from the table
## 3. Update Member Roles
- **Precondition:** User is admin, member exists
- **Steps:**
1. In the 'Members' table, locate the member row
2. Click 'Edit' in the Action column
3. Change role in the edit dialog/modal
4. Save changes
- **Expected:** Member role is updated in the table
## 4. Configure SSO
- **Precondition:** User is admin
- **Steps:**
1. In the 'Authenticated Domains' section, locate the domain row
2. Click 'Configure SSO' or 'Edit Google Auth' as available
3. Complete SSO provider configuration in the modal/dialog
4. Save settings
- **Expected:** SSO is configured for the domain
## 5. Login via SSO
- **Precondition:** SSO is configured
- **Steps:**
1. Log out from the app
2. On the login page, click 'Login with SSO'
3. Complete SSO login flow
- **Expected:** User is logged in via SSO

View File

@@ -1,39 +0,0 @@
# Notification Channels E2E Scenarios (Updated)
## 1. Add a New Notification Channel
- **Precondition:** User is admin
- **Steps:**
1. Click 'New Alert Channel' button
2. In the 'New Notification Channel' form, fill in required fields (Name, Type, Webhook URL, etc.)
3. (Optional) Toggle 'Send resolved alerts'
4. (Optional) Click 'Test' to send a test notification
5. Click 'Save' to add the channel
- **Expected:** Channel is added and listed in the table
## 2. Test Notification Channel
- **Precondition:** Channel is being created or edited
- **Steps:**
1. In the 'New Notification Channel' or 'Edit Notification Channel' form, click 'Test'
- **Expected:** Test notification is sent (UI feedback/confirmation should be checked)
## 3. Remove a Notification Channel
- **Precondition:** Channel is added
- **Steps:**
1. In the table, locate the channel row
2. Click 'Delete' in the Action column
3. Confirm removal if prompted
- **Expected:** Channel is removed from the table
## 4. Update Notification Channel Settings
- **Precondition:** Channel is added
- **Steps:**
1. In the table, locate the channel row
2. Click 'Edit' in the Action column
3. In the 'Edit Notification Channel' form, update fields as needed
4. (Optional) Click 'Test' to send a test notification
5. Click 'Save' to update the channel
- **Expected:** Settings are updated

View File

@@ -1,199 +0,0 @@
# SigNoz Test Plan Validation Report
This report documents the validation of the E2E test plan against the current live application using Playwright MCP. Each module is reviewed for coverage, gaps, and required updates.
---
## Home Module
- **Coverage:**
- Widgets for logs, traces, metrics, dashboards, alerts, services, saved views, onboarding checklist
- Quick access buttons: Explore Logs, Create dashboard, Create an alert
- **Gaps/Updates:**
- Add scenarios for checklist interactions (e.g., “Ill do this later”, progress tracking)
- Add scenarios for Saved Views and cross-module links
- Add scenario for onboarding checklist completion
---
## Logs Module
- **Coverage:**
- Explorer, Pipelines, Views tabs
- Filtering by service, environment, severity, host, k8s, etc.
- Search, save view, create alert, add to dashboard, export, view mode switching
- **Gaps/Updates:**
- Add scenario for quick filter customization
- Add scenario for “Old Explorer” button
- Add scenario for frequency chart toggle
- Add scenario for “Stage & Run Query” workflow
---
## Traces Module
- **Coverage:**
- Tabs: Explorer, Funnels, Views
- Filtering by name, error status, duration, environment, function, service, RPC, status code, HTTP, trace ID, etc.
- Search, save view, create alert, add to dashboard, export, view mode switching (List, Traces, Time Series, Table)
- Pagination, quick filter customization, group by, aggregation
- **Gaps/Updates:**
- Add scenario for quick filter customization
- Add scenario for “Stage & Run Query” workflow
- Add scenario for all view modes (List, Traces, Time Series, Table)
- Add scenario for group by/aggregation
- Add scenario for trace detail navigation (clicking on trace row)
- Add scenario for Funnels tab (create/edit/delete funnel)
- Add scenario for Views tab (manage saved views)
---
## Metrics Module
- **Coverage:**
- Tabs: Summary, Explorer, Views
- Filtering by metric, type, unit, etc.
- Search, save view, add to dashboard, export, view mode switching (chart, table, proportion view)
- Pagination, group by, aggregation, custom queries
- **Gaps/Updates:**
- Add scenario for Proportion View in Summary
- Add scenario for all view modes (chart, table, proportion)
- Add scenario for group by/aggregation
- Add scenario for custom queries in Explorer
- Add scenario for Views tab (manage saved views)
---
## Dashboards Module
- **Coverage:**
- List, search, and filter dashboards
- Create new dashboard (button and template link)
- Edit, delete, and view dashboard details
- Add/edit/delete widgets (implied by dashboard detail)
- Pagination through dashboards
- **Gaps/Updates:**
- Add scenario for browsing dashboard templates (external link)
- Add scenario for requesting new template
- Add scenario for dashboard owner and creation info
- Add scenario for dashboard tags and filtering by tags
- Add scenario for dashboard sharing (if available)
- Add scenario for dashboard image/preview
---
## Messaging Queues Module
- **Coverage:**
- Overview tab: queue metrics, filters (Service Name, Span Name, Msg System, Destination, Kind)
- Search across all columns
- Pagination of queue data
- Sync and Share buttons
- Tabs for Kafka and Celery
- **Gaps/Updates:**
- Add scenario for Kafka tab (detailed metrics, actions)
- Add scenario for Celery tab (detailed metrics, actions)
- Add scenario for filter combinations and edge cases
- Add scenario for sharing queue data
- Add scenario for time range selection
---
## External APIs Module
- **Coverage:**
- Accessed via side navigation under MORE
- Explorer tab: domain, endpoints, last used, rate, error %, avg. latency
- Filters: Deployment Environment, Service Name, Rpc Method, Show IP addresses
- Table pagination
- Share and Stage & Run Query buttons
- **Gaps/Updates:**
- Add scenario for customizing quick filters
- Add scenario for running and staging queries
- Add scenario for sharing API data
- Add scenario for edge cases in filters and table data
---
## Alerts Module
- **Coverage:**
- Alert Rules tab: list, search, create (New Alert), edit, delete, enable/disable, severity, labels, actions
- Triggered Alerts tab (visible in tablist)
- Configuration tab (visible in tablist)
- Table pagination
- **Gaps/Updates:**
- Add scenario for triggered alerts (view, acknowledge, resolve)
- Add scenario for alert configuration (settings, integrations)
- Add scenario for edge cases in alert creation and management
- Add scenario for searching and filtering alerts
---
## Integrations Module
- **Coverage:**
- Integrations tab: list, search, configure (e.g., AWS), request new integration
- One-click setup for AWS monitoring
- Request more integrations (form)
- **Gaps/Updates:**
- Add scenario for configuring integrations (step-by-step)
- Add scenario for searching and filtering integrations
- Add scenario for requesting new integrations
- Add scenario for edge cases (e.g., failed configuration)
---
## Exceptions Module
- **Coverage:**
- All Exceptions: list, search, filter (Deployment Environment, Service Name, Host Name, K8s Cluster/Deployment/Namespace, Net Peer Name)
- Table: Exception Type, Error Message, Count, Last Seen, First Seen, Application
- Pagination
- Exception detail links
- Share and Stage & Run Query buttons
- **Gaps/Updates:**
- Add scenario for exception detail view
- Add scenario for advanced filtering and edge cases
- Add scenario for sharing and running queries
- Add scenario for error grouping and navigation
---
## Service Map Module
- **Coverage:**
- Service Map visualization (main graph)
- Filters: environment, resource attributes
- Time range selection
- Sync and Share buttons
- **Gaps/Updates:**
- Add scenario for interacting with the map (zoom, pan, select service)
- Add scenario for filtering and edge cases
- Add scenario for sharing the map
- Add scenario for time range and environment combinations
---
## Billing Module
- **Coverage:**
- Billing overview: cost monitoring, invoices, CSV download (disabled), manage billing (disabled)
- Teams Cloud section
- Billing table: Unit, Data Ingested, Price per Unit, Cost (Billing period to date)
- **Gaps/Updates:**
- Add scenario for invoice download and management (when enabled)
- Add scenario for cost monitoring and edge cases
- Add scenario for billing table data validation
- Add scenario for permissions and access control
---
## Usage Explorer Module
- **Status:**
- Not accessible in the current environment. Removing from test plan flows.
---
## [Next modules will be filled as validation proceeds]

View File

@@ -1,42 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Account Settings - View and Assert Static Controls', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Assert General section and controls (confirmed by DOM)
await expect(
page.getByLabel('My Settings').getByText('General'),
).toBeVisible();
await expect(page.getByText('Manage your account settings.')).toBeVisible();
await expect(page.getByRole('button', { name: 'Update name' })).toBeVisible();
await expect(
page.getByRole('button', { name: 'Reset password' }),
).toBeVisible();
// Assert User Preferences section and controls (confirmed by DOM)
await expect(page.getByText('User Preferences')).toBeVisible();
await expect(
page.getByText('Tailor the SigNoz console to work according to your needs.'),
).toBeVisible();
await expect(page.getByText('Select your theme')).toBeVisible();
const themeSelector = page.getByTestId('theme-selector');
await expect(themeSelector.getByText('Dark')).toBeVisible();
await expect(themeSelector.getByText('Light')).toBeVisible();
await expect(themeSelector.getByText('System')).toBeVisible();
await expect(page.getByTestId('timezone-adaptation-switch')).toBeVisible();
await expect(page.getByTestId('side-nav-pinned-switch')).toBeVisible();
});

View File

@@ -1,42 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('API Keys Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click API Keys tab in the settings sidebar (by data-testid)
await page.getByTestId('api-keys').click();
// Assert heading and subheading
await expect(page.getByRole('heading', { name: 'API Keys' })).toBeVisible();
await expect(
page.getByText('Create and manage API keys for the SigNoz API'),
).toBeVisible();
// Assert presence of New Key button
const newKeyBtn = page.getByRole('button', { name: 'New Key' });
await expect(newKeyBtn).toBeVisible();
// Assert table columns
await expect(page.getByText('Last used').first()).toBeVisible();
await expect(page.getByText('Expired').first()).toBeVisible();
// Assert at least one API key row with action buttons
// Select the first action cell's first button (icon button)
const firstActionCell = page.locator('table tr').nth(1).locator('td').last();
const deleteBtn = firstActionCell.locator('button').first();
await expect(deleteBtn).toBeVisible();
});

View File

@@ -1,71 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
// E2E: Billing Settings - View Billing Information and Button Actions
test('View Billing Information and Button Actions', async ({
page,
context,
}) => {
// Ensure user is logged in
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Billing tab in the settings sidebar (by data-testid)
await page.getByTestId('billing').click();
// Wait for billing chart/data to finish loading
await page.getByText('loading').first().waitFor({ state: 'hidden' });
// Assert visibility of subheading (unique)
await expect(
page.getByText(
'Manage your billing information, invoices, and monitor costs.',
),
).toBeVisible();
// Assert visibility of Teams Cloud heading
await expect(page.getByRole('heading', { name: 'Teams Cloud' })).toBeVisible();
// Assert presence of summary and detailed tables
await expect(page.getByText('TOTAL SPENT')).toBeVisible();
await expect(page.getByText('Data Ingested')).toBeVisible();
await expect(page.getByText('Price per Unit')).toBeVisible();
await expect(page.getByText('Cost (Billing period to date)')).toBeVisible();
// Assert presence of alert and note
await expect(
page.getByText('Your current billing period is from', { exact: false }),
).toBeVisible();
await expect(
page.getByText('Billing metrics are updated once every 24 hours.'),
).toBeVisible();
// Test Download CSV button
const [download] = await Promise.all([
page.waitForEvent('download'),
page.getByRole('button', { name: 'cloud-download Download CSV' }).click(),
]);
// Optionally, check download file name
expect(download.suggestedFilename()).toContain('billing_usage');
// Test Manage Billing button (opens Stripe in new tab)
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.getByTestId('header-billing-button').click(),
]);
await newPage.waitForLoadState();
expect(newPage.url()).toContain('stripe.com');
await newPage.close();
});

View File

@@ -1,52 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Custom Domain Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Custom Domain tab in the settings sidebar (by data-testid)
await page.getByTestId('custom-domain').click();
// Wait for custom domain chart/data to finish loading
await page.getByText('loading').first().waitFor({ state: 'hidden' });
// Assert heading and subheading
await expect(
page.getByRole('heading', { name: 'Custom Domain Settings' }),
).toBeVisible();
await expect(
page.getByText('Personalize your workspace domain effortlessly.'),
).toBeVisible();
// Assert presence of Customize teams URL button
const customizeBtn = page.getByRole('button', {
name: 'Customize teams URL',
});
await expect(customizeBtn).toBeVisible();
await customizeBtn.click();
// Assert modal/dialog fields and buttons
await expect(
page.getByRole('dialog', { name: 'Customize your teams URL' }),
).toBeVisible();
await expect(page.getByLabel('Teams URL subdomain')).toBeVisible();
await expect(
page.getByRole('button', { name: 'Apply Changes' }),
).toBeVisible();
await expect(page.getByRole('button', { name: 'Close' })).toBeVisible();
// Close the modal
await page.getByRole('button', { name: 'Close' }).click();
});

View File

@@ -1,32 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('View General Settings', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click General tab in the settings sidebar (by data-testid)
await page.getByTestId('general').click();
// Wait for General tab to be visible
await page.getByRole('tabpanel', { name: 'General' }).waitFor();
// Assert visibility of definitive/static elements
await expect(page.getByRole('heading', { name: 'Metrics' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Traces' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Logs' })).toBeVisible();
await expect(page.getByText('Please')).toBeVisible();
await expect(page.getByRole('link', { name: 'email us' })).toBeVisible();
});

View File

@@ -1,48 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Ingestion Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Ingestion tab in the settings sidebar (by data-testid)
await page.getByTestId('ingestion').click();
// Assert heading and subheading (Integrations page)
await expect(
page.getByRole('heading', { name: 'Integrations' }),
).toBeVisible();
await expect(
page.getByText('Manage Integrations for this workspace'),
).toBeVisible();
// Assert presence of search box
await expect(
page.getByPlaceholder('Search for an integration...'),
).toBeVisible();
// Assert at least one data source with Configure button
const configureBtn = page.getByRole('button', { name: 'Configure' }).first();
await expect(configureBtn).toBeVisible();
// Assert Request more integrations section
await expect(
page.getByText(
"Can't find what youre looking for? Request more integrations",
),
).toBeVisible();
await expect(page.getByPlaceholder('Enter integration name...')).toBeVisible();
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
});

View File

@@ -1,48 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Integrations Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Integrations tab in the settings sidebar (by data-testid)
await page.getByTestId('integrations').click();
// Assert heading and subheading
await expect(
page.getByRole('heading', { name: 'Integrations' }),
).toBeVisible();
await expect(
page.getByText('Manage Integrations for this workspace'),
).toBeVisible();
// Assert presence of search box
await expect(
page.getByPlaceholder('Search for an integration...'),
).toBeVisible();
// Assert at least one integration with Configure button
const configureBtn = page.getByRole('button', { name: 'Configure' }).first();
await expect(configureBtn).toBeVisible();
// Assert Request more integrations section
await expect(
page.getByText(
"Can't find what youre looking for? Request more integrations",
),
).toBeVisible();
await expect(page.getByPlaceholder('Enter integration name...')).toBeVisible();
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
});

View File

@@ -1,56 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Members & SSO Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Members & SSO tab in the settings sidebar (by data-testid)
await page.getByTestId('members-sso').click();
// Assert headings and tables
await expect(
page.getByRole('heading', { name: /Members \(\d+\)/ }),
).toBeVisible();
await expect(
page.getByRole('heading', { name: /Pending Invites \(\d+\)/ }),
).toBeVisible();
await expect(
page.getByRole('heading', { name: 'Authenticated Domains' }),
).toBeVisible();
// Assert Invite Members button is visible and clickable
const inviteBtn = page.getByRole('button', { name: /Invite Members/ });
await expect(inviteBtn).toBeVisible();
await inviteBtn.click();
// Assert Invite Members modal/dialog appears (modal title is unique)
await expect(page.getByText('Invite team members').first()).toBeVisible();
// Close the modal (use unique 'Close' button)
await page.getByRole('button', { name: 'Close' }).click();
// Assert Edit and Delete buttons are present for at least one member
const editBtn = page.getByRole('button', { name: /Edit/ }).first();
const deleteBtn = page.getByRole('button', { name: /Delete/ }).first();
await expect(editBtn).toBeVisible();
await expect(deleteBtn).toBeVisible();
// Assert Add Domains button is visible
await expect(page.getByRole('button', { name: /Add Domains/ })).toBeVisible();
// Assert Configure SSO or Edit Google Auth button is visible for at least one domain
const ssoBtn = page
.getByRole('button', { name: /Configure SSO|Edit Google Auth/ })
.first();
await expect(ssoBtn).toBeVisible();
});

View File

@@ -1,57 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Notification Channels Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Notification Channels tab in the settings sidebar (by data-testid)
await page.getByTestId('notification-channels').click();
// Wait for loading to finish
await page.getByText('loading').first().waitFor({ state: 'hidden' });
// Assert presence of New Alert Channel button
const newChannelBtn = page.getByRole('button', { name: /New Alert Channel/ });
await expect(newChannelBtn).toBeVisible();
// Assert table columns
await expect(page.getByText('Name')).toBeVisible();
await expect(page.getByText('Type')).toBeVisible();
await expect(page.getByText('Action')).toBeVisible();
// Click New Alert Channel and assert modal fields/buttons
await newChannelBtn.click();
await expect(
page.getByRole('heading', { name: 'New Notification Channel' }),
).toBeVisible();
await expect(page.getByLabel('Name')).toBeVisible();
await expect(page.getByLabel('Type')).toBeVisible();
await expect(page.getByLabel('Webhook URL')).toBeVisible();
await expect(
page.getByRole('switch', { name: 'Send resolved alerts' }),
).toBeVisible();
await expect(page.getByRole('button', { name: 'Save' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Test' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Back' })).toBeVisible();
// Close modal
await page.getByRole('button', { name: 'Back' }).click();
// Assert Edit and Delete buttons for at least one channel
const editBtn = page.getByRole('button', { name: 'Edit' }).first();
const deleteBtn = page.getByRole('button', { name: 'Delete' }).first();
await expect(editBtn).toBeVisible();
await expect(deleteBtn).toBeVisible();
});

View File

@@ -1,35 +0,0 @@
import { Page } from '@playwright/test';
// Read credentials from environment variables
const username = process.env.LOGIN_USERNAME;
const password = process.env.LOGIN_PASSWORD;
const baseURL = process.env.BASE_URL;
/**
* Ensures the user is logged in. If not, performs the login steps.
* Follows the MCP process step-by-step.
*/
export async function ensureLoggedIn(page: Page): Promise<void> {
// if already in home page, return
if (await page.url().includes('/home')) {
return;
}
if (!username || !password) {
throw new Error(
'E2E_EMAIL and E2E_PASSWORD environment variables must be set.',
);
}
await page.goto(`${baseURL}/login`);
await page.getByTestId('email').click();
await page.getByTestId('email').fill(username);
await page.getByTestId('initiate_login').click();
await page.getByTestId('password').click();
await page.getByTestId('password').fill(password);
await page.getByRole('button', { name: 'Login' }).click();
await page
.getByText('Hello there, Welcome to your')
.waitFor({ state: 'visible' });
}

View File

@@ -1,7 +1,8 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<base href="[[.BaseHref]]" />
<meta
http-equiv="Cache-Control"
content="no-cache, no-store, must-revalidate, max-age: 0"
@@ -39,12 +40,12 @@
<meta
data-react-helmet="true"
property="og:image"
content="/images/signoz-hero-image.webp"
content="[[.BaseHref]]images/signoz-hero-image.webp"
/>
<meta
data-react-helmet="true"
name="twitter:image"
content="/images/signoz-hero-image.webp"
content="[[.BaseHref]]images/signoz-hero-image.webp"
/>
<meta
data-react-helmet="true"
@@ -59,7 +60,7 @@
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
<meta name="robots" content="noindex" />
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
<link data-react-helmet="true" rel="shortcut icon" href="favicon.ico" />
</head>
<body data-theme="default">
<script>
@@ -67,8 +68,16 @@
// Mirrors the logic in ThemeProvider (hooks/useDarkMode/index.tsx).
(function () {
try {
var theme = localStorage.getItem('THEME');
var autoSwitch = localStorage.getItem('THEME_AUTO_SWITCH') === 'true';
// When served under a URL prefix (e.g. /signoz/), storage keys are scoped
// to that prefix by the React app (see utils/storage.ts getScopedKey).
// Read the <base> tag — already populated by the Go template — to derive
// the same prefix here, before any JS module has loaded.
var basePath =
(document.querySelector('base') || {}).getAttribute('href') || '/';
var prefix = basePath === '/' ? '' : basePath;
var theme = localStorage.getItem(prefix + 'THEME');
var autoSwitch =
localStorage.getItem(prefix + 'THEME_AUTO_SWITCH') === 'true';
if (autoSwitch) {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
@@ -136,7 +145,7 @@
})(document, 'script');
}
</script>
<link rel="stylesheet" href="/css/uPlot.min.css" />
<link rel="stylesheet" href="css/uPlot.min.css" />
<script type="module" src="./src/index.tsx"></script>
</body>
</html>

View File

@@ -42,13 +42,13 @@ window.getComputedStyle = function (
} catch {
// Return a minimal CSSStyleDeclaration so callers (testing-library, Radix UI)
// see the element as visible and without animations.
return ({
return {
display: '',
visibility: '',
opacity: '1',
animationName: 'none',
getPropertyValue: () => '',
} as unknown) as CSSStyleDeclaration;
} as unknown as CSSStyleDeclaration;
}
};

5
frontend/knip.json Normal file
View File

@@ -0,0 +1,5 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"project": ["src/**/*.ts", "src/**/*.tsx"],
"ignore": ["src/api/generated/**/*.ts"]
}

View File

@@ -9,7 +9,7 @@
"build": "vite build",
"preview": "vite preview",
"prettify": "oxfmt",
"fmt": "echo 'Disabled due to migration' || oxfmt --check",
"fmt": "oxfmt --check",
"lint": "oxlint ./src && stylelint \"src/**/*.scss\"",
"lint:js": "oxlint ./src",
"lint:generated": "oxlint ./src/api/generated --fix",
@@ -44,7 +44,6 @@
"@mdx-js/loader": "2.3.0",
"@mdx-js/react": "2.3.0",
"@monaco-editor/react": "^4.3.1",
"@playwright/test": "1.55.1",
"@radix-ui/react-tabs": "1.0.4",
"@radix-ui/react-tooltip": "1.0.7",
"@sentry/react": "8.41.0",
@@ -64,7 +63,6 @@
"@visx/shape": "3.5.0",
"@visx/tooltip": "3.3.0",
"@vitejs/plugin-react": "5.1.4",
"@xstate/react": "^3.0.0",
"ansi-to-html": "0.7.2",
"antd": "5.11.0",
"antd-table-saveas-excel": "2.2.1",
@@ -147,7 +145,6 @@
"vite": "npm:rolldown-vite@7.3.1",
"vite-plugin-html": "3.2.2",
"web-vitals": "^0.2.4",
"xstate": "^4.31.0",
"zod": "4.3.6",
"zustand": "5.0.11"
},
@@ -215,9 +212,9 @@
"msw": "1.3.2",
"npm-run-all": "latest",
"orval": "7.18.0",
"oxfmt": "0.41.0",
"oxlint": "1.59.0",
"oxlint-tsgolint": "0.20.0",
"oxfmt": "0.47.0",
"oxlint": "1.62.0",
"oxlint-tsgolint": "0.22.1",
"portfinder-sync": "^0.0.2",
"postcss": "8.5.6",
"postcss-scss": "4.0.9",
@@ -241,9 +238,12 @@
},
"lint-staged": {
"*.(js|jsx|ts|tsx)": [
"echo 'Disabled due to migration' || oxfmt --check",
"oxlint --fix",
"oxfmt --write",
"sh scripts/typecheck-staged.sh"
],
"*.(scss|css)": [
"stylelint"
]
},
"resolutions": {

View File

@@ -1,95 +0,0 @@
import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
import path from 'path';
// Read from ".env" file.
dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e/tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Run tests in parallel even in CI - optimized for GitHub Actions free tier */
workers: process.env.CI ? 2 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL:
process.env.SIGNOZ_E2E_BASE_URL || 'https://app.us.staging.signoz.cloud',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
colorScheme: 'dark',
locale: 'en-US',
viewport: { width: 1280, height: 720 },
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
launchOptions: { args: ['--start-maximized'] },
viewport: null,
colorScheme: 'dark',
locale: 'en-US',
baseURL: 'https://app.us.staging.signoz.cloud',
trace: 'on-first-retry',
},
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://localhost:3000',
// reuseExistingServer: !process.env.CI,
// },
});

View File

@@ -0,0 +1,174 @@
/**
* Rule: no-raw-absolute-path
*
* Catches patterns that break at runtime when the app is served from a
* sub-path (e.g. /signoz/):
*
* 1. window.open(path, '_blank')
* → use openInNewTab(path) which calls withBasePath internally
*
* 2. window.location.origin + path / `${window.location.origin}${path}`
* → use getAbsoluteUrl(path)
*
* 3. frontendBaseUrl: window.location.origin (bare origin usage)
* → use getBaseUrl() to include the base path
*
* 4. window.location.href = path
* → use withBasePath(path) or navigate() for internal navigation
*
* External URLs (first arg starts with "http") are explicitly allowed.
*/
function isOriginAccess(node) {
return (
node.type === 'MemberExpression' &&
!node.computed &&
node.property.name === 'origin' &&
node.object.type === 'MemberExpression' &&
!node.object.computed &&
node.object.property.name === 'location' &&
node.object.object.type === 'Identifier' &&
node.object.object.name === 'window'
);
}
function isHrefAccess(node) {
return (
node.type === 'MemberExpression' &&
!node.computed &&
node.property.name === 'href' &&
node.object.type === 'MemberExpression' &&
!node.object.computed &&
node.object.property.name === 'location' &&
node.object.object.type === 'Identifier' &&
node.object.object.name === 'window'
);
}
function isExternalUrl(node) {
if (node.type === 'Literal' && typeof node.value === 'string') {
return node.value.startsWith('http://') || node.value.startsWith('https://');
}
if (node.type === 'TemplateLiteral' && node.quasis.length > 0) {
const raw = node.quasis[0].value.raw;
return raw.startsWith('http://') || raw.startsWith('https://');
}
return false;
}
// window.open(withBasePath(x)) and window.open(getAbsoluteUrl(x)) are already safe.
function isSafeHelperCall(node) {
return (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(node.callee.name === 'withBasePath' || node.callee.name === 'getAbsoluteUrl')
);
}
export default {
meta: {
type: 'suggestion',
docs: {
description:
'Disallow raw window.open and origin-concatenation patterns that miss the runtime base path',
category: 'Base Path Safety',
},
schema: [],
messages: {
windowOpen:
'Use openInNewTab(path) instead of window.open(path, "_blank") — openInNewTab prepends the base path automatically.',
originConcat:
'Use getAbsoluteUrl(path) instead of window.location.origin + path — getAbsoluteUrl prepends the base path automatically.',
originDirect:
'Use getBaseUrl() instead of window.location.origin — getBaseUrl includes the base path.',
hrefAssign:
'Use withBasePath(path) or navigate() instead of window.location.href = path — ensures the base path is included.',
},
},
create(context) {
return {
// window.open(path, ...) — allow only external first-arg URLs
CallExpression(node) {
const { callee, arguments: args } = node;
if (
callee.type !== 'MemberExpression' ||
callee.object.type !== 'Identifier' ||
callee.object.name !== 'window' ||
callee.property.name !== 'open'
) {
return;
}
if (args.length === 0) {
return;
}
if (isExternalUrl(args[0])) {
return;
}
if (isSafeHelperCall(args[0])) {
return;
}
context.report({ node, messageId: 'windowOpen' });
},
// window.location.origin + path
BinaryExpression(node) {
if (node.operator !== '+') {
return;
}
if (isOriginAccess(node.left) || isOriginAccess(node.right)) {
context.report({ node, messageId: 'originConcat' });
}
},
// `${window.location.origin}${path}`
TemplateLiteral(node) {
if (node.expressions.some(isOriginAccess)) {
context.report({ node, messageId: 'originConcat' });
}
},
// window.location.origin used directly (not in concatenation)
// Catches: frontendBaseUrl: window.location.origin
MemberExpression(node) {
if (!isOriginAccess(node)) {
return;
}
const parent = node.parent;
// Skip if parent is BinaryExpression with + (handled by BinaryExpression visitor)
if (parent.type === 'BinaryExpression' && parent.operator === '+') {
return;
}
// Skip if inside TemplateLiteral (handled by TemplateLiteral visitor)
if (parent.type === 'TemplateLiteral') {
return;
}
context.report({ node, messageId: 'originDirect' });
},
// window.location.href = path
AssignmentExpression(node) {
if (node.operator !== '=') {
return;
}
if (!isHrefAccess(node.left)) {
return;
}
// Allow external URLs
if (isExternalUrl(node.right)) {
return;
}
// Allow safe helper calls
if (isSafeHelperCall(node.right)) {
return;
}
context.report({ node, messageId: 'hrefAssign' });
},
};
},
};

View File

@@ -8,6 +8,7 @@
import noZustandGetStateInHooks from './rules/no-zustand-getstate-in-hooks.mjs';
import noNavigatorClipboard from './rules/no-navigator-clipboard.mjs';
import noUnsupportedAssetPattern from './rules/no-unsupported-asset-pattern.mjs';
import noRawAbsolutePath from './rules/no-raw-absolute-path.mjs';
export default {
meta: {
@@ -17,5 +18,6 @@ export default {
'no-zustand-getstate-in-hooks': noZustandGetStateInHooks,
'no-navigator-clipboard': noNavigatorClipboard,
'no-unsupported-asset-pattern': noUnsupportedAssetPattern,
'no-raw-absolute-path': noRawAbsolutePath,
},
};

View File

@@ -1,16 +0,0 @@
RULE: All test code for this repo must be generated by following the step-by-step Playwright MCP process as described below.
- You are a playwright test generator.
- You are given a scenario and you need to generate a playwright test for it.
- Use login util if not logged in.
- DO NOT generate test code based on the scenario alone.
- DO run steps one by one using the tools provided by the Playwright MCP.
- Only after all steps are completed, emit a Playwright TypeScript test that uses @playwright/test based on message history
- Gather correct selectors before writing the test
- DO NOT valiate for dynamic content in the tests, only validate for the correctness with meta data
- Always inspect the DOM at each navigation or interaction step to determine the correct selector for the next action. Do not assume selectors, confirm via inspection before proceeding.
- Assert visibility of definitive/static elements in the UI (such as labels, headings, or section titles) rather than dynamic values or content that may change between runs.
- Save generated test file in the tests directory
- Execute the test file and iterate until the test passes

View File

@@ -16,5 +16,6 @@
"roles": "Roles",
"role_details": "Role Details",
"members": "Members",
"service_accounts": "Service Accounts"
"service_accounts": "Service Accounts",
"mcp_server": "MCP Server"
}

View File

@@ -53,5 +53,6 @@
"METER": "SigNoz | Meter",
"ROLES_SETTINGS": "SigNoz | Roles",
"MEMBERS_SETTINGS": "SigNoz | Members",
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
"MCP_SERVER": "SigNoz | MCP Server"
}

View File

@@ -16,5 +16,6 @@
"roles": "Roles",
"role_details": "Role Details",
"members": "Members",
"service_accounts": "Service Accounts"
"service_accounts": "Service Accounts",
"mcp_server": "MCP Server"
}

View File

@@ -76,5 +76,6 @@
"METER": "SigNoz | Meter",
"ROLES_SETTINGS": "SigNoz | Roles",
"MEMBERS_SETTINGS": "SigNoz | Members",
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
"MCP_SERVER": "SigNoz | MCP Server"
}

View File

@@ -105,7 +105,7 @@ function createMockLicense(
status: '',
updated_at: '0',
},
state: LicenseState.ACTIVE,
state: LicenseState.ACTIVATED,
status: LicenseStatus.VALID,
platform: LicensePlatform.CLOUD,
created_at: '0',
@@ -931,7 +931,7 @@ describe('PrivateRoute', () => {
isFetchingActiveLicense: false,
activeLicense: createMockLicense({
platform: LicensePlatform.CLOUD,
state: LicenseState.ACTIVE,
state: LicenseState.ACTIVATED,
}),
},
isCloudUser: true,
@@ -1522,7 +1522,7 @@ describe('PrivateRoute', () => {
isFetchingActiveLicense: false,
activeLicense: createMockLicense({
platform: LicensePlatform.CLOUD,
state: LicenseState.ACTIVE,
state: LicenseState.ACTIVATED,
}),
trialInfo: createMockTrialInfo({ workSpaceBlock: false }),
user: createMockUser({ role: USER_ROLES.ADMIN as ROLES }),

View File

@@ -2,6 +2,7 @@ import { initReactI18next } from 'react-i18next';
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { getBasePath } from 'utils/basePath';
import cacheBursting from '../../i18n-translations-hash.json';
@@ -24,7 +25,7 @@ i18n
const ns = namespace[0];
const pathkey = `/${language}/${ns}`;
const hash = cacheBursting[pathkey as keyof typeof cacheBursting] || '';
return `/locales/${language}/${namespace}.json?h=${hash}`;
return `${getBasePath()}locales/${language}/${namespace}.json?h=${hash}`;
},
},
react: {

View File

@@ -0,0 +1,130 @@
/**
* localstorage/get — lazy migration tests.
*
* basePath is memoized at module init, so each describe block re-imports the
* module with a fresh DOM state via jest.isolateModules.
*/
type GetModule = typeof import('../get');
function loadGetModule(href: string): GetModule {
const base = document.createElement('base');
base.setAttribute('href', href);
document.head.append(base);
let mod!: GetModule;
jest.isolateModules(() => {
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
mod = require('../get');
});
return mod;
}
afterEach(() => {
for (const el of document.head.querySelectorAll('base')) {
el.remove();
}
localStorage.clear();
});
describe('get — root path "/"', () => {
it('reads the bare key', () => {
const { default: get } = loadGetModule('/');
localStorage.setItem('AUTH_TOKEN', 'tok');
expect(get('AUTH_TOKEN')).toBe('tok');
});
it('returns null when key is absent', () => {
const { default: get } = loadGetModule('/');
expect(get('MISSING')).toBeNull();
});
it('does NOT promote bare keys (no-op at root)', () => {
const { default: get } = loadGetModule('/');
localStorage.setItem('THEME', 'light');
get('THEME');
// bare key must still be present — no migration at root
expect(localStorage.getItem('THEME')).toBe('light');
});
});
describe('get — prefixed path "/signoz/"', () => {
it('reads an already-scoped key directly', () => {
const { default: get } = loadGetModule('/signoz/');
localStorage.setItem('/signoz/AUTH_TOKEN', 'scoped-tok');
expect(get('AUTH_TOKEN')).toBe('scoped-tok');
});
it('returns null when neither scoped nor bare key exists', () => {
const { default: get } = loadGetModule('/signoz/');
expect(get('MISSING')).toBeNull();
});
it('lazy-migrates bare key to scoped key on first read', () => {
const { default: get } = loadGetModule('/signoz/');
localStorage.setItem('AUTH_TOKEN', 'old-tok');
const result = get('AUTH_TOKEN');
expect(result).toBe('old-tok');
expect(localStorage.getItem('/signoz/AUTH_TOKEN')).toBe('old-tok');
expect(localStorage.getItem('AUTH_TOKEN')).toBeNull();
});
it('scoped key takes precedence over bare key', () => {
const { default: get } = loadGetModule('/signoz/');
localStorage.setItem('AUTH_TOKEN', 'bare-tok');
localStorage.setItem('/signoz/AUTH_TOKEN', 'scoped-tok');
expect(get('AUTH_TOKEN')).toBe('scoped-tok');
// bare key left untouched — scoped already existed
expect(localStorage.getItem('AUTH_TOKEN')).toBe('bare-tok');
});
it('subsequent reads after migration use scoped key (no double-write)', () => {
const { default: get } = loadGetModule('/signoz/');
localStorage.setItem('THEME', 'dark');
get('THEME'); // triggers migration
localStorage.removeItem('THEME'); // simulate bare key gone
// second read still finds the scoped key
expect(get('THEME')).toBe('dark');
});
});
describe('get — two-prefix isolation', () => {
it('/signoz/ and /testing/ do not share migrated values', () => {
localStorage.setItem('THEME', 'light');
const base1 = document.createElement('base');
base1.setAttribute('href', '/signoz/');
document.head.append(base1);
let getSignoz!: GetModule['default'];
jest.isolateModules(() => {
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
getSignoz = require('../get').default;
});
base1.remove();
// migrate bare → /signoz/THEME
getSignoz('THEME');
const base2 = document.createElement('base');
base2.setAttribute('href', '/testing/');
document.head.append(base2);
let getTesting!: GetModule['default'];
jest.isolateModules(() => {
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
getTesting = require('../get').default;
});
base2.remove();
// /testing/ prefix: bare key already gone, scoped key does not exist
expect(getTesting('THEME')).toBeNull();
expect(localStorage.getItem('/signoz/THEME')).toBe('light');
expect(localStorage.getItem('/testing/THEME')).toBeNull();
});
});
export {};

View File

@@ -1,7 +1,26 @@
/* oxlint-disable no-restricted-globals */
import { getBasePath } from 'utils/basePath';
import { getScopedKey } from 'utils/storage';
const get = (key: string): string | null => {
try {
return localStorage.getItem(key);
} catch (e) {
const scopedKey = getScopedKey(key);
const value = localStorage.getItem(scopedKey);
// Lazy migration: if running under a URL prefix and the scoped key doesn't
// exist yet, fall back to the bare key (written by a previous root deployment).
// Promote it to the scoped key and remove the bare key so future reads are fast.
if (value === null && getBasePath() !== '/') {
const bare = localStorage.getItem(key);
if (bare !== null) {
localStorage.setItem(scopedKey, bare);
localStorage.removeItem(key);
return bare;
}
}
return value;
} catch {
return '';
}
};

View File

@@ -1,8 +1,11 @@
/* oxlint-disable no-restricted-globals */
import { getScopedKey } from 'utils/storage';
const remove = (key: string): boolean => {
try {
window.localStorage.removeItem(key);
localStorage.removeItem(getScopedKey(key));
return true;
} catch (e) {
} catch {
return false;
}
};

View File

@@ -1,8 +1,11 @@
/* oxlint-disable no-restricted-globals */
import { getScopedKey } from 'utils/storage';
const set = (key: string, value: string): boolean => {
try {
localStorage.setItem(key, value);
localStorage.setItem(getScopedKey(key), value);
return true;
} catch (e) {
} catch {
return false;
}
};

View File

@@ -0,0 +1,81 @@
/**
* sessionstorage/get — lazy migration tests.
* Mirrors the localStorage get tests; same logic, different storage.
*/
type GetModule = typeof import('../get');
function loadGetModule(href: string): GetModule {
const base = document.createElement('base');
base.setAttribute('href', href);
document.head.append(base);
let mod!: GetModule;
jest.isolateModules(() => {
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
mod = require('../get');
});
return mod;
}
afterEach(() => {
for (const el of document.head.querySelectorAll('base')) {
el.remove();
}
sessionStorage.clear();
});
describe('get — root path "/"', () => {
it('reads the bare key', () => {
const { default: get } = loadGetModule('/');
sessionStorage.setItem('retry-lazy-refreshed', 'true');
expect(get('retry-lazy-refreshed')).toBe('true');
});
it('returns null when key is absent', () => {
const { default: get } = loadGetModule('/');
expect(get('MISSING')).toBeNull();
});
it('does NOT promote bare keys at root', () => {
const { default: get } = loadGetModule('/');
sessionStorage.setItem('retry-lazy-refreshed', 'true');
get('retry-lazy-refreshed');
expect(sessionStorage.getItem('retry-lazy-refreshed')).toBe('true');
});
});
describe('get — prefixed path "/signoz/"', () => {
it('reads an already-scoped key directly', () => {
const { default: get } = loadGetModule('/signoz/');
sessionStorage.setItem('/signoz/retry-lazy-refreshed', 'true');
expect(get('retry-lazy-refreshed')).toBe('true');
});
it('returns null when neither scoped nor bare key exists', () => {
const { default: get } = loadGetModule('/signoz/');
expect(get('MISSING')).toBeNull();
});
it('lazy-migrates bare key to scoped key on first read', () => {
const { default: get } = loadGetModule('/signoz/');
sessionStorage.setItem('retry-lazy-refreshed', 'true');
const result = get('retry-lazy-refreshed');
expect(result).toBe('true');
expect(sessionStorage.getItem('/signoz/retry-lazy-refreshed')).toBe('true');
expect(sessionStorage.getItem('retry-lazy-refreshed')).toBeNull();
});
it('scoped key takes precedence over bare key', () => {
const { default: get } = loadGetModule('/signoz/');
sessionStorage.setItem('retry-lazy-refreshed', 'bare');
sessionStorage.setItem('/signoz/retry-lazy-refreshed', 'scoped');
expect(get('retry-lazy-refreshed')).toBe('scoped');
expect(sessionStorage.getItem('retry-lazy-refreshed')).toBe('bare');
});
});
export {};

View File

@@ -0,0 +1,27 @@
/* oxlint-disable no-restricted-globals */
import { getBasePath } from 'utils/basePath';
import { getScopedKey } from 'utils/storage';
const get = (key: string): string | null => {
try {
const scopedKey = getScopedKey(key);
const value = sessionStorage.getItem(scopedKey);
// Lazy migration: same pattern as localStorage — promote bare keys written
// by a previous root deployment to the scoped key on first read.
if (value === null && getBasePath() !== '/') {
const bare = sessionStorage.getItem(key);
if (bare !== null) {
sessionStorage.setItem(scopedKey, bare);
sessionStorage.removeItem(key);
return bare;
}
}
return value;
} catch {
return '';
}
};
export default get;

View File

@@ -0,0 +1,13 @@
/* oxlint-disable no-restricted-globals */
import { getScopedKey } from 'utils/storage';
const remove = (key: string): boolean => {
try {
sessionStorage.removeItem(getScopedKey(key));
return true;
} catch {
return false;
}
};
export default remove;

View File

@@ -0,0 +1,13 @@
/* oxlint-disable no-restricted-globals */
import { getScopedKey } from 'utils/storage';
const set = (key: string, value: string): boolean => {
try {
sessionStorage.setItem(getScopedKey(key), value);
return true;
} catch {
return false;
}
};
export default set;

View File

@@ -1,15 +0,0 @@
import { ApiV3Instance as axios } from 'api';
import { ApiResponse } from 'types/api';
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import { QueryRangePayload } from 'types/api/metrics/getQueryRange';
interface IQueryRangeFormat {
compositeQuery: ICompositeMetricQuery;
}
export const getQueryRangeFormat = (
props?: Partial<QueryRangePayload>,
): Promise<IQueryRangeFormat> =>
axios
.post<ApiResponse<IQueryRangeFormat>>('/query_range/format', props)
.then((res) => res.data.data);

View File

@@ -1,24 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
const getDynamicConfigs = async (): Promise<
SuccessResponse<PayloadProps> | ErrorResponse
> => {
try {
const response = await axios.get(`/configs`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getDynamicConfigs;

View File

@@ -104,7 +104,7 @@ describe('getFieldKeys API', () => {
const result = await getFieldKeys('traces');
// Verify the returned structure matches SuccessResponseV2 format
expect(result).toEqual({
expect(result).toStrictEqual({
httpStatusCode: 200,
data: mockSuccessResponse.data.data,
});

View File

@@ -199,7 +199,7 @@ describe('getFieldValues API', () => {
const result = await getFieldValues('traces', 'service.name');
// Verify the returned structure matches SuccessResponseV2 format
expect(result).toEqual({
expect(result).toStrictEqual({
httpStatusCode: 200,
data: expect.objectContaining({
values: expect.any(Object),

View File

@@ -18,10 +18,12 @@ import type {
} from 'react-query';
import type {
CreateDashboardV2201,
CreatePublicDashboard201,
CreatePublicDashboardPathParameters,
DashboardtypesPostablePublicDashboardDTO,
DashboardtypesUpdatablePublicDashboardDTO,
Dashboardtypesv2PostableDashboardDTO,
DeletePublicDashboardPathParameters,
GetPublicDashboard200,
GetPublicDashboardData200,
@@ -634,3 +636,88 @@ export const invalidateGetPublicDashboardWidgetQueryRange = async (
return queryClient;
};
/**
* This endpoint creates a v2-shape dashboard with structured metadata, a typed data tree, and resolved tags.
* @summary Create dashboard (v2)
*/
export const createDashboardV2 = (
dashboardtypesv2PostableDashboardDTO: BodyType<Dashboardtypesv2PostableDashboardDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateDashboardV2201>({
url: `/api/v2/dashboards`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: dashboardtypesv2PostableDashboardDTO,
signal,
});
};
export const getCreateDashboardV2MutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createDashboardV2>>,
TError,
{ data: BodyType<Dashboardtypesv2PostableDashboardDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createDashboardV2>>,
TError,
{ data: BodyType<Dashboardtypesv2PostableDashboardDTO> },
TContext
> => {
const mutationKey = ['createDashboardV2'];
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 createDashboardV2>>,
{ data: BodyType<Dashboardtypesv2PostableDashboardDTO> }
> = (props) => {
const { data } = props ?? {};
return createDashboardV2(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateDashboardV2MutationResult = NonNullable<
Awaited<ReturnType<typeof createDashboardV2>>
>;
export type CreateDashboardV2MutationBody =
BodyType<Dashboardtypesv2PostableDashboardDTO>;
export type CreateDashboardV2MutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create dashboard (v2)
*/
export const useCreateDashboardV2 = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createDashboardV2>>,
TError,
{ data: BodyType<Dashboardtypesv2PostableDashboardDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createDashboardV2>>,
TError,
{ data: BodyType<Dashboardtypesv2PostableDashboardDTO> },
TContext
> => {
const mutationOptions = getCreateDashboardV2MutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -13,7 +13,9 @@ import type {
import type {
InframonitoringtypesPostableHostsDTO,
InframonitoringtypesPostablePodsDTO,
ListHosts200,
ListPods200,
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
@@ -21,7 +23,7 @@ import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Returns a paginated list of hosts with key infrastructure metrics: CPU usage (%), memory usage (%), I/O wait (%), disk usage (%), and 15-minute load average. Each host includes its current status (active/inactive based on metrics reported in the last 10 minutes) and metadata attributes (e.g., os.type). Supports filtering via a filter expression, filtering by host status, custom groupBy to aggregate hosts by any attribute, ordering by any of the five metrics, and pagination via offset/limit. The response type is 'list' for the default host.name grouping or 'grouped_list' for custom groupBy keys. Also reports missing required metrics and whether the requested time range falls before the data retention boundary.
* Returns a paginated list of hosts with key infrastructure metrics: CPU usage (%), memory usage (%), I/O wait (%), disk usage (%), and 15-minute load average. Each host includes its current status (active/inactive based on metrics reported in the last 10 minutes) and metadata attributes (e.g., os.type). Supports filtering via a filter expression, filtering by host status, custom groupBy to aggregate hosts by any attribute, ordering by any of the five metrics, and pagination via offset/limit. The response type is 'list' for the default host.name grouping or 'grouped_list' for custom groupBy keys. Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (cpu, memory, wait, load15, diskUsage) return -1 as a sentinel when no data is available for that field.
* @summary List Hosts for Infra Monitoring
*/
export const listHosts = (
@@ -104,3 +106,87 @@ export const useListHosts = <
return useMutation(mutationOptions);
};
/**
* Returns a paginated list of Kubernetes pods with key metrics: CPU usage, CPU request/limit utilization, memory working set, memory request/limit utilization, current pod phase (pending/running/succeeded/failed/unknown), and pod age (ms since start time). Each pod includes metadata attributes (namespace, node, workload owner such as deployment/statefulset/daemonset/job/cronjob, cluster). Supports filtering via a filter expression, custom groupBy to aggregate pods by any attribute, ordering by any of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit. The response type is 'list' for the default k8s.pod.uid grouping (each row is one pod with its current phase) or 'grouped_list' for custom groupBy keys (each row aggregates pods in the group with per-phase counts: pendingPodCount, runningPodCount, succeededPodCount, failedPodCount, unknownPodCount derived from each pod's latest phase in the window). Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (podCPU, podCPURequest, podCPULimit, podMemory, podMemoryRequest, podMemoryLimit, podAge) return -1 as a sentinel when no data is available for that field.
* @summary List Pods for Infra Monitoring
*/
export const listPods = (
inframonitoringtypesPostablePodsDTO: BodyType<InframonitoringtypesPostablePodsDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListPods200>({
url: `/api/v2/infra_monitoring/pods`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: inframonitoringtypesPostablePodsDTO,
signal,
});
};
export const getListPodsMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof listPods>>,
TError,
{ data: BodyType<InframonitoringtypesPostablePodsDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof listPods>>,
TError,
{ data: BodyType<InframonitoringtypesPostablePodsDTO> },
TContext
> => {
const mutationKey = ['listPods'];
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 listPods>>,
{ data: BodyType<InframonitoringtypesPostablePodsDTO> }
> = (props) => {
const { data } = props ?? {};
return listPods(data);
};
return { mutationFn, ...mutationOptions };
};
export type ListPodsMutationResult = NonNullable<
Awaited<ReturnType<typeof listPods>>
>;
export type ListPodsMutationBody =
BodyType<InframonitoringtypesPostablePodsDTO>;
export type ListPodsMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List Pods for Infra Monitoring
*/
export const useListPods = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof listPods>>,
TError,
{ data: BodyType<InframonitoringtypesPostablePodsDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof listPods>>,
TError,
{ data: BodyType<InframonitoringtypesPostablePodsDTO> },
TContext
> => {
const mutationOptions = getListPodsMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -1,398 +0,0 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import { useMutation, useQuery } from 'react-query';
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import type {
DeleteLLMPricingRulePathParameters,
GetLLMPricingRule200,
GetLLMPricingRulePathParameters,
ListLLMPricingRules200,
ListLLMPricingRulesParams,
LlmpricingruletypesUpdatableLLMPricingRulesDTO,
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Returns all LLM pricing rules for the authenticated org, with pagination.
* @summary List pricing rules
*/
export const listLLMPricingRules = (
params?: ListLLMPricingRulesParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListLLMPricingRules200>({
url: `/api/v1/llm_pricing_rules`,
method: 'GET',
params,
signal,
});
};
export const getListLLMPricingRulesQueryKey = (
params?: ListLLMPricingRulesParams,
) => {
return [`/api/v1/llm_pricing_rules`, ...(params ? [params] : [])] as const;
};
export const getListLLMPricingRulesQueryOptions = <
TData = Awaited<ReturnType<typeof listLLMPricingRules>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListLLMPricingRulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListLLMPricingRulesQueryKey(params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listLLMPricingRules>>
> = ({ signal }) => listLLMPricingRules(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListLLMPricingRulesQueryResult = NonNullable<
Awaited<ReturnType<typeof listLLMPricingRules>>
>;
export type ListLLMPricingRulesQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List pricing rules
*/
export function useListLLMPricingRules<
TData = Awaited<ReturnType<typeof listLLMPricingRules>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListLLMPricingRulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListLLMPricingRulesQueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List pricing rules
*/
export const invalidateListLLMPricingRules = async (
queryClient: QueryClient,
params?: ListLLMPricingRulesParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListLLMPricingRulesQueryKey(params) },
options,
);
return queryClient;
};
/**
* Single write endpoint used by both the user and the Zeus sync job. Per-rule match is by id, then sourceId, then insert. Override rows (is_override=true) are fully preserved when the request does not provide isOverride; only synced_at is stamped.
* @summary Bulk update pricing rules
*/
export const updateLLMPricingRules = (
llmpricingruletypesUpdatableLLMPricingRulesDTO: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/llm_pricing_rules`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: llmpricingruletypesUpdatableLLMPricingRulesDTO,
});
};
export const getUpdateLLMPricingRulesMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
> => {
const mutationKey = ['updateLLMPricingRules'];
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 updateLLMPricingRules>>,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> }
> = (props) => {
const { data } = props ?? {};
return updateLLMPricingRules(data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateLLMPricingRulesMutationResult = NonNullable<
Awaited<ReturnType<typeof updateLLMPricingRules>>
>;
export type UpdateLLMPricingRulesMutationBody =
BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO>;
export type UpdateLLMPricingRulesMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Bulk update pricing rules
*/
export const useUpdateLLMPricingRules = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
> => {
const mutationOptions = getUpdateLLMPricingRulesMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Hard-deletes a pricing rule. If auto-synced, it will be recreated on the next sync cycle.
* @summary Delete a pricing rule
*/
export const deleteLLMPricingRule = ({
id,
}: DeleteLLMPricingRulePathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/llm_pricing_rules/${id}`,
method: 'DELETE',
});
};
export const getDeleteLLMPricingRuleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
> => {
const mutationKey = ['deleteLLMPricingRule'];
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 deleteLLMPricingRule>>,
{ pathParams: DeleteLLMPricingRulePathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteLLMPricingRule(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteLLMPricingRuleMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteLLMPricingRule>>
>;
export type DeleteLLMPricingRuleMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete a pricing rule
*/
export const useDeleteLLMPricingRule = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
> => {
const mutationOptions = getDeleteLLMPricingRuleMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Returns a single LLM pricing rule by ID.
* @summary Get a pricing rule
*/
export const getLLMPricingRule = (
{ id }: GetLLMPricingRulePathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetLLMPricingRule200>({
url: `/api/v1/llm_pricing_rules/${id}`,
method: 'GET',
signal,
});
};
export const getGetLLMPricingRuleQueryKey = ({
id,
}: GetLLMPricingRulePathParameters) => {
return [`/api/v1/llm_pricing_rules/${id}`] as const;
};
export const getGetLLMPricingRuleQueryOptions = <
TData = Awaited<ReturnType<typeof getLLMPricingRule>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetLLMPricingRulePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetLLMPricingRuleQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getLLMPricingRule>>
> = ({ signal }) => getLLMPricingRule({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetLLMPricingRuleQueryResult = NonNullable<
Awaited<ReturnType<typeof getLLMPricingRule>>
>;
export type GetLLMPricingRuleQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get a pricing rule
*/
export function useGetLLMPricingRule<
TData = Awaited<ReturnType<typeof getLLMPricingRule>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetLLMPricingRulePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetLLMPricingRuleQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get a pricing rule
*/
export const invalidateGetLLMPricingRule = async (
queryClient: QueryClient,
{ id }: GetLLMPricingRulePathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetLLMPricingRuleQueryKey({ id }) },
options,
);
return queryClient;
};

View File

@@ -471,7 +471,7 @@ export const getObjects = (
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetObjects200>({
url: `/api/v1/roles/${id}/relation/${relation}/objects`,
url: `/api/v1/roles/${id}/relations/${relation}/objects`,
method: 'GET',
signal,
});
@@ -481,7 +481,7 @@ export const getGetObjectsQueryKey = ({
id,
relation,
}: GetObjectsPathParameters) => {
return [`/api/v1/roles/${id}/relation/${relation}/objects`] as const;
return [`/api/v1/roles/${id}/relations/${relation}/objects`] as const;
};
export const getGetObjectsQueryOptions = <
@@ -574,7 +574,7 @@ export const patchObjects = (
authtypesPatchableObjectsDTO: BodyType<AuthtypesPatchableObjectsDTO>,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v1/roles/${id}/relation/${relation}/objects`,
url: `/api/v1/roles/${id}/relations/${relation}/objects`,
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
data: authtypesPatchableObjectsDTO,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,787 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import { useMutation, useQuery } from 'react-query';
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import type {
CreateSpanMapper201,
CreateSpanMapperGroup201,
CreateSpanMapperPathParameters,
DeleteSpanMapperGroupPathParameters,
DeleteSpanMapperPathParameters,
ListSpanMapperGroups200,
ListSpanMapperGroupsParams,
ListSpanMappers200,
ListSpanMappersPathParameters,
RenderErrorResponseDTO,
SpantypesPostableSpanMapperDTO,
SpantypesPostableSpanMapperGroupDTO,
SpantypesUpdatableSpanMapperDTO,
SpantypesUpdatableSpanMapperGroupDTO,
UpdateSpanMapperGroupPathParameters,
UpdateSpanMapperPathParameters,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Returns all span attribute mapping groups for the authenticated org.
* @summary List span attribute mapping groups
*/
export const listSpanMapperGroups = (
params?: ListSpanMapperGroupsParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListSpanMapperGroups200>({
url: `/api/v1/span_mapper_groups`,
method: 'GET',
params,
signal,
});
};
export const getListSpanMapperGroupsQueryKey = (
params?: ListSpanMapperGroupsParams,
) => {
return [`/api/v1/span_mapper_groups`, ...(params ? [params] : [])] as const;
};
export const getListSpanMapperGroupsQueryOptions = <
TData = Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListSpanMapperGroupsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListSpanMapperGroupsQueryKey(params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listSpanMapperGroups>>
> = ({ signal }) => listSpanMapperGroups(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListSpanMapperGroupsQueryResult = NonNullable<
Awaited<ReturnType<typeof listSpanMapperGroups>>
>;
export type ListSpanMapperGroupsQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List span attribute mapping groups
*/
export function useListSpanMapperGroups<
TData = Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListSpanMapperGroupsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListSpanMapperGroupsQueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List span attribute mapping groups
*/
export const invalidateListSpanMapperGroups = async (
queryClient: QueryClient,
params?: ListSpanMapperGroupsParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListSpanMapperGroupsQueryKey(params) },
options,
);
return queryClient;
};
/**
* Creates a new span attribute mapping group for the org.
* @summary Create a span attribute mapping group
*/
export const createSpanMapperGroup = (
spantypesPostableSpanMapperGroupDTO: BodyType<SpantypesPostableSpanMapperGroupDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateSpanMapperGroup201>({
url: `/api/v1/span_mapper_groups`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: spantypesPostableSpanMapperGroupDTO,
signal,
});
};
export const getCreateSpanMapperGroupMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
> => {
const mutationKey = ['createSpanMapperGroup'];
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 createSpanMapperGroup>>,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> }
> = (props) => {
const { data } = props ?? {};
return createSpanMapperGroup(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateSpanMapperGroupMutationResult = NonNullable<
Awaited<ReturnType<typeof createSpanMapperGroup>>
>;
export type CreateSpanMapperGroupMutationBody =
BodyType<SpantypesPostableSpanMapperGroupDTO>;
export type CreateSpanMapperGroupMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create a span attribute mapping group
*/
export const useCreateSpanMapperGroup = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
> => {
const mutationOptions = getCreateSpanMapperGroupMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Hard-deletes a mapping group and cascades to all its mappers.
* @summary Delete a span attribute mapping group
*/
export const deleteSpanMapperGroup = ({
groupId,
}: DeleteSpanMapperGroupPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}`,
method: 'DELETE',
});
};
export const getDeleteSpanMapperGroupMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
> => {
const mutationKey = ['deleteSpanMapperGroup'];
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 deleteSpanMapperGroup>>,
{ pathParams: DeleteSpanMapperGroupPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteSpanMapperGroup(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteSpanMapperGroupMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>
>;
export type DeleteSpanMapperGroupMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete a span attribute mapping group
*/
export const useDeleteSpanMapperGroup = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
> => {
const mutationOptions = getDeleteSpanMapperGroupMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Partially updates an existing mapping group's name, condition, or enabled state.
* @summary Update a span attribute mapping group
*/
export const updateSpanMapperGroup = (
{ groupId }: UpdateSpanMapperGroupPathParameters,
spantypesUpdatableSpanMapperGroupDTO: BodyType<SpantypesUpdatableSpanMapperGroupDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}`,
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
data: spantypesUpdatableSpanMapperGroupDTO,
});
};
export const getUpdateSpanMapperGroupMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
> => {
const mutationKey = ['updateSpanMapperGroup'];
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 updateSpanMapperGroup>>,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateSpanMapperGroup(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateSpanMapperGroupMutationResult = NonNullable<
Awaited<ReturnType<typeof updateSpanMapperGroup>>
>;
export type UpdateSpanMapperGroupMutationBody =
BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
export type UpdateSpanMapperGroupMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update a span attribute mapping group
*/
export const useUpdateSpanMapperGroup = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
> => {
const mutationOptions = getUpdateSpanMapperGroupMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Returns all mappers belonging to a mapping group.
* @summary List span mappers for a group
*/
export const listSpanMappers = (
{ groupId }: ListSpanMappersPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListSpanMappers200>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers`,
method: 'GET',
signal,
});
};
export const getListSpanMappersQueryKey = ({
groupId,
}: ListSpanMappersPathParameters) => {
return [`/api/v1/span_mapper_groups/${groupId}/span_mappers`] as const;
};
export const getListSpanMappersQueryOptions = <
TData = Awaited<ReturnType<typeof listSpanMappers>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ groupId }: ListSpanMappersPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMappers>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListSpanMappersQueryKey({ groupId });
const queryFn: QueryFunction<Awaited<ReturnType<typeof listSpanMappers>>> = ({
signal,
}) => listSpanMappers({ groupId }, signal);
return {
queryKey,
queryFn,
enabled: !!groupId,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof listSpanMappers>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListSpanMappersQueryResult = NonNullable<
Awaited<ReturnType<typeof listSpanMappers>>
>;
export type ListSpanMappersQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List span mappers for a group
*/
export function useListSpanMappers<
TData = Awaited<ReturnType<typeof listSpanMappers>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ groupId }: ListSpanMappersPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMappers>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListSpanMappersQueryOptions({ groupId }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List span mappers for a group
*/
export const invalidateListSpanMappers = async (
queryClient: QueryClient,
{ groupId }: ListSpanMappersPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListSpanMappersQueryKey({ groupId }) },
options,
);
return queryClient;
};
/**
* Adds a new mapper to the specified mapping group.
* @summary Create a span mapper
*/
export const createSpanMapper = (
{ groupId }: CreateSpanMapperPathParameters,
spantypesPostableSpanMapperDTO: BodyType<SpantypesPostableSpanMapperDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateSpanMapper201>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: spantypesPostableSpanMapperDTO,
signal,
});
};
export const getCreateSpanMapperMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
> => {
const mutationKey = ['createSpanMapper'];
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 createSpanMapper>>,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return createSpanMapper(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateSpanMapperMutationResult = NonNullable<
Awaited<ReturnType<typeof createSpanMapper>>
>;
export type CreateSpanMapperMutationBody =
BodyType<SpantypesPostableSpanMapperDTO>;
export type CreateSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create a span mapper
*/
export const useCreateSpanMapper = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
> => {
const mutationOptions = getCreateSpanMapperMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Hard-deletes a mapper from a mapping group.
* @summary Delete a span mapper
*/
export const deleteSpanMapper = ({
groupId,
mapperId,
}: DeleteSpanMapperPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers/${mapperId}`,
method: 'DELETE',
});
};
export const getDeleteSpanMapperMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
> => {
const mutationKey = ['deleteSpanMapper'];
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 deleteSpanMapper>>,
{ pathParams: DeleteSpanMapperPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteSpanMapper(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteSpanMapperMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteSpanMapper>>
>;
export type DeleteSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete a span mapper
*/
export const useDeleteSpanMapper = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
> => {
const mutationOptions = getDeleteSpanMapperMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Partially updates an existing mapper's field context, config, or enabled state.
* @summary Update a span mapper
*/
export const updateSpanMapper = (
{ groupId, mapperId }: UpdateSpanMapperPathParameters,
spantypesUpdatableSpanMapperDTO: BodyType<SpantypesUpdatableSpanMapperDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers/${mapperId}`,
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
data: spantypesUpdatableSpanMapperDTO,
});
};
export const getUpdateSpanMapperMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
> => {
const mutationKey = ['updateSpanMapper'];
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 updateSpanMapper>>,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateSpanMapper(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateSpanMapperMutationResult = NonNullable<
Awaited<ReturnType<typeof updateSpanMapper>>
>;
export type UpdateSpanMapperMutationBody =
BodyType<SpantypesUpdatableSpanMapperDTO>;
export type UpdateSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update a span mapper
*/
export const useUpdateSpanMapper = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
> => {
const mutationOptions = getUpdateSpanMapperMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -0,0 +1,123 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import { useMutation } from 'react-query';
import type {
MutationFunction,
UseMutationOptions,
UseMutationResult,
} from 'react-query';
import type {
GetWaterfall200,
GetWaterfallPathParameters,
RenderErrorResponseDTO,
TracedetailtypesPostableWaterfallDTO,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Returns the waterfall view of spans for a given trace ID with tree structure, metadata, and windowed pagination
* @summary Get waterfall view for a trace
*/
export const getWaterfall = (
{ traceID }: GetWaterfallPathParameters,
tracedetailtypesPostableWaterfallDTO: BodyType<TracedetailtypesPostableWaterfallDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetWaterfall200>({
url: `/api/v3/traces/${traceID}/waterfall`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: tracedetailtypesPostableWaterfallDTO,
signal,
});
};
export const getGetWaterfallMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof getWaterfall>>,
TError,
{
pathParams: GetWaterfallPathParameters;
data: BodyType<TracedetailtypesPostableWaterfallDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof getWaterfall>>,
TError,
{
pathParams: GetWaterfallPathParameters;
data: BodyType<TracedetailtypesPostableWaterfallDTO>;
},
TContext
> => {
const mutationKey = ['getWaterfall'];
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 getWaterfall>>,
{
pathParams: GetWaterfallPathParameters;
data: BodyType<TracedetailtypesPostableWaterfallDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return getWaterfall(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type GetWaterfallMutationResult = NonNullable<
Awaited<ReturnType<typeof getWaterfall>>
>;
export type GetWaterfallMutationBody =
BodyType<TracedetailtypesPostableWaterfallDTO>;
export type GetWaterfallMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get waterfall view for a trace
*/
export const useGetWaterfall = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof getWaterfall>>,
TError,
{
pathParams: GetWaterfallPathParameters;
data: BodyType<TracedetailtypesPostableWaterfallDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof getWaterfall>>,
TError,
{
pathParams: GetWaterfallPathParameters;
data: BodyType<TracedetailtypesPostableWaterfallDTO>;
},
TContext
> => {
const mutationOptions = getGetWaterfallMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -1,5 +1,6 @@
import {
interceptorRejected,
interceptorsRequestBasePath,
interceptorsRequestResponse,
interceptorsResponse,
} from 'api';
@@ -17,7 +18,9 @@ export const GeneratedAPIInstance = <T>(
return generatedAPIAxiosInstance({ ...config }).then(({ data }) => data);
};
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
generatedAPIAxiosInstance.interceptors.response.use(
interceptorsResponse,
interceptorRejected,

View File

@@ -1,25 +0,0 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import {
GlobalConfigData,
GlobalConfigDataProps,
} from 'types/api/globalConfig/types';
const getGlobalConfig = async (): Promise<
SuccessResponseV2<GlobalConfigData>
> => {
try {
const response = await axios.get<GlobalConfigDataProps>(`/global/config`);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getGlobalConfig;

View File

@@ -11,6 +11,7 @@ import axios, {
import { ENVIRONMENT } from 'constants/env';
import { Events } from 'constants/events';
import { LOCALSTORAGE } from 'constants/localStorage';
import { getBasePath } from 'utils/basePath';
import { eventEmitter } from 'utils/getEventEmitter';
import apiV1, { apiAlertManager, apiV2, apiV3, apiV4, apiV5 } from './apiV1';
@@ -67,6 +68,39 @@ export const interceptorsRequestResponse = (
return value;
};
// Strips the leading '/' from path and joins with base — idempotent if already prefixed.
// e.g. prependBase('/signoz/', '/api/v1/') → '/signoz/api/v1/'
function prependBase(base: string, path: string): string {
return path.startsWith(base) ? path : base + path.slice(1);
}
// Prepends the runtime base path to outgoing requests so API calls work under
// a URL prefix (e.g. /signoz/api/v1/…). No-op for root deployments and dev
// (dev baseURL is a full http:// URL, not an absolute path).
export const interceptorsRequestBasePath = (
value: InternalAxiosRequestConfig,
): InternalAxiosRequestConfig => {
const basePath = getBasePath();
if (basePath === '/') {
return value;
}
if (value.baseURL?.startsWith('/')) {
// Production relative baseURL: '/api/v1/' → '/signoz/api/v1/'
value.baseURL = prependBase(basePath, value.baseURL);
} else if (value.baseURL?.startsWith('http')) {
// Dev absolute baseURL (VITE_FRONTEND_API_ENDPOINT): 'https://host/api/v1/' → 'https://host/signoz/api/v1/'
const url = new URL(value.baseURL);
url.pathname = prependBase(basePath, url.pathname);
value.baseURL = url.toString();
} else if (!value.baseURL && value.url?.startsWith('/')) {
// Orval-generated client (empty baseURL, path in url): '/api/signoz/v1/rules' → '/signoz/api/signoz/v1/rules'
value.url = prependBase(basePath, value.url);
}
return value;
};
export const interceptorRejected = async (
value: AxiosResponse<any>,
): Promise<AxiosResponse<any>> => {
@@ -133,6 +167,7 @@ const instance = axios.create({
});
instance.interceptors.request.use(interceptorsRequestResponse);
instance.interceptors.request.use(interceptorsRequestBasePath);
instance.interceptors.response.use(interceptorsResponse, interceptorRejected);
export const AxiosAlertManagerInstance = axios.create({
@@ -147,6 +182,7 @@ ApiV2Instance.interceptors.response.use(
interceptorRejected,
);
ApiV2Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV2Instance.interceptors.request.use(interceptorsRequestBasePath);
// axios V3
export const ApiV3Instance = axios.create({
@@ -158,6 +194,7 @@ ApiV3Instance.interceptors.response.use(
interceptorRejected,
);
ApiV3Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV3Instance.interceptors.request.use(interceptorsRequestBasePath);
//
// axios V4
@@ -170,6 +207,7 @@ ApiV4Instance.interceptors.response.use(
interceptorRejected,
);
ApiV4Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV4Instance.interceptors.request.use(interceptorsRequestBasePath);
//
// axios V5
@@ -182,6 +220,7 @@ ApiV5Instance.interceptors.response.use(
interceptorRejected,
);
ApiV5Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV5Instance.interceptors.request.use(interceptorsRequestBasePath);
//
// axios Base
@@ -194,6 +233,7 @@ LogEventAxiosInstance.interceptors.response.use(
interceptorRejectedBase,
);
LogEventAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
LogEventAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
//
AxiosAlertManagerInstance.interceptors.response.use(
@@ -201,6 +241,7 @@ AxiosAlertManagerInstance.interceptors.response.use(
interceptorRejected,
);
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestResponse);
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestBasePath);
export { apiV1 };
export default instance;

View File

@@ -1,44 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError, AxiosResponse } from 'axios';
import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder';
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8s/constants';
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
BaseAutocompleteData,
IQueryAutocompleteResponse,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
export const getHostAttributeKeys = async (
searchText = '',
entity: InfraMonitoringEntity,
): Promise<SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse> => {
try {
const response: AxiosResponse<{
data: IQueryAutocompleteResponse;
}> = await axios.get(
`/${entity}/attribute_keys?dataSource=metrics&searchText=${searchText}`,
{
params: {
limit: 500,
},
},
);
const payload: BaseAutocompleteData[] =
response.data.data.attributeKeys?.map(({ id: _, ...item }) => ({
...item,
id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder),
})) || [];
return {
statusCode: 200,
error: null,
message: response.statusText,
payload: { attributeKeys: payload },
};
} catch (e) {
return ErrorResponseHandler(e as AxiosError);
}
};

View File

@@ -1,40 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import createQueryParams from 'lib/createQueryParams';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
IAttributeValuesResponse,
IGetAttributeValuesPayload,
} from 'types/api/queryBuilder/getAttributesValues';
export const getInfraAttributesValues = async ({
dataSource,
attributeKey,
filterAttributeKeyDataType,
tagType,
searchText,
aggregateAttribute,
}: IGetAttributeValuesPayload): Promise<
SuccessResponse<IAttributeValuesResponse> | ErrorResponse
> => {
try {
const response = await axios.get(
`/hosts/attribute_values?${createQueryParams({
dataSource,
attributeKey,
searchText,
aggregateAttribute,
})}&filterAttributeKeyDataType=${filterAttributeKeyDataType}&tagType=${tagType}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -39,8 +39,8 @@ jest.mock('axios', () => {
describe('interceptorRejected', () => {
beforeEach(() => {
jest.clearAllMocks();
((axios as unknown) as jest.Mock).mockResolvedValue({ data: 'success' });
((axios.isAxiosError as unknown) as jest.Mock).mockReturnValue(true);
(axios as unknown as jest.Mock).mockResolvedValue({ data: 'success' });
(axios.isAxiosError as unknown as jest.Mock).mockReturnValue(true);
});
it('should preserve array payload structure when retrying a 401 request', async () => {
@@ -49,7 +49,7 @@ describe('interceptorRejected', () => {
{ relation: 'assignee', object: { resource: { name: 'editor' } } },
];
const error = ({
const error = {
response: {
status: 401,
config: {
@@ -67,7 +67,7 @@ describe('interceptorRejected', () => {
headers: new AxiosHeaders(),
data: JSON.stringify(arrayPayload),
},
} as unknown) as AxiosResponse;
} as unknown as AxiosResponse;
try {
await interceptorRejected(error);
@@ -75,17 +75,17 @@ describe('interceptorRejected', () => {
// Expected to reject after retry
}
const mockAxiosFn = (axios as unknown) as jest.Mock;
expect(mockAxiosFn.mock.calls.length).toBe(1);
const mockAxiosFn = axios as unknown as jest.Mock;
expect(mockAxiosFn.mock.calls).toHaveLength(1);
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
expect(Array.isArray(JSON.parse(retryCallConfig.data))).toBe(true);
expect(JSON.parse(retryCallConfig.data)).toEqual(arrayPayload);
expect(JSON.parse(retryCallConfig.data)).toStrictEqual(arrayPayload);
});
it('should preserve object payload structure when retrying a 401 request', async () => {
const objectPayload = { key: 'value', nested: { data: 123 } };
const error = ({
const error = {
response: {
status: 401,
config: {
@@ -103,7 +103,7 @@ describe('interceptorRejected', () => {
headers: new AxiosHeaders(),
data: JSON.stringify(objectPayload),
},
} as unknown) as AxiosResponse;
} as unknown as AxiosResponse;
try {
await interceptorRejected(error);
@@ -111,14 +111,14 @@ describe('interceptorRejected', () => {
// Expected to reject after retry
}
const mockAxiosFn = (axios as unknown) as jest.Mock;
expect(mockAxiosFn.mock.calls.length).toBe(1);
const mockAxiosFn = axios as unknown as jest.Mock;
expect(mockAxiosFn.mock.calls).toHaveLength(1);
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
expect(JSON.parse(retryCallConfig.data)).toEqual(objectPayload);
expect(JSON.parse(retryCallConfig.data)).toStrictEqual(objectPayload);
});
it('should handle undefined data gracefully when retrying', async () => {
const error = ({
const error = {
response: {
status: 401,
config: {
@@ -136,7 +136,7 @@ describe('interceptorRejected', () => {
headers: new AxiosHeaders(),
data: undefined,
},
} as unknown) as AxiosResponse;
} as unknown as AxiosResponse;
try {
await interceptorRejected(error);
@@ -144,8 +144,8 @@ describe('interceptorRejected', () => {
// Expected to reject after retry
}
const mockAxiosFn = (axios as unknown) as jest.Mock;
expect(mockAxiosFn.mock.calls.length).toBe(1);
const mockAxiosFn = axios as unknown as jest.Mock;
expect(mockAxiosFn.mock.calls).toHaveLength(1);
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
expect(retryCallConfig.data).toBeUndefined();
});

View File

@@ -3,13 +3,16 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
import { ENVIRONMENT } from 'constants/env';
import { LOCALSTORAGE } from 'constants/localStorage';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { withBasePath } from 'utils/basePath';
// 10 min in ms
const TIMEOUT_IN_MS = 10 * 60 * 1000;
export const LiveTail = (queryParams: string): EventSourcePolyfill =>
new EventSourcePolyfill(
`${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`,
ENVIRONMENT.baseURL
? `${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`
: withBasePath(`${apiV1}logs/tail?${queryParams}`),
{
headers: {
Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`,

View File

@@ -1,26 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/metrics/getDBOverview';
const getDBOverView = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.get(
`/service/dbOverview?&start=${props.start}&end=${props.end}&service=${props.service}&step=${props.step}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getDBOverView;

View File

@@ -1,29 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
PayloadProps,
Props,
} from 'types/api/metrics/getExternalAverageDuration';
const getExternalAverageDuration = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.get(
`/service/externalAvgDuration?&start=${props.start}&end=${props.end}&service=${props.service}&step=${props.step}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getExternalAverageDuration;

View File

@@ -1,26 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/metrics/getExternalError';
const getExternalError = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.get(
`/service/externalErrors?&start=${props.start}&end=${props.end}&service=${props.service}&step=${props.step}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getExternalError;

View File

@@ -1,26 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/metrics/getExternalService';
const getExternalService = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.get(
`/service/external?&start=${props.start}&end=${props.end}&service=${props.service}&step=${props.step}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getExternalService;

View File

@@ -1,16 +0,0 @@
import axios from 'api';
import { PayloadProps, Props } from 'types/api/metrics/getServiceOverview';
const getServiceOverview = async (props: Props): Promise<PayloadProps> => {
const response = await axios.post(`/service/overview`, {
start: `${props.start}`,
end: `${props.end}`,
service: props.service,
step: props.step,
tags: props.selectedTags,
});
return response.data;
};
export default getServiceOverview;

View File

@@ -9,9 +9,8 @@ const getRetentionV2 = async (): Promise<
SuccessResponseV2<PayloadProps<'logs'>>
> => {
try {
const response = await ApiV2Instance.get<PayloadProps<'logs'>>(
`/settings/ttl`,
);
const response =
await ApiV2Instance.get<PayloadProps<'logs'>>(`/settings/ttl`);
return {
httpStatusCode: response.status,

View File

@@ -1,32 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { formUrlParams } from 'container/TraceDetail/utils';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { GetTraceItemProps, PayloadProps } from 'types/api/trace/getTraceItem';
const getTraceItem = async (
props: GetTraceItemProps,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.request<PayloadProps>({
url: `/traces/${props.id}${formUrlParams({
spanId: props.spanId,
levelUp: props.levelUp,
levelDown: props.levelDown,
})}`,
method: 'get',
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getTraceItem;

View File

@@ -1,25 +0,0 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/preferences/get';
import { OrgPreference } from 'types/api/preferences/preference';
const getPreference = async (
props: Props,
): Promise<SuccessResponseV2<OrgPreference>> => {
try {
const response = await axios.get<PayloadProps>(
`/org/preferences/${props.name}`,
);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getPreference;

View File

@@ -52,18 +52,18 @@ describe('convertV5ResponseToLegacy', () => {
alias: '__result_0',
meta: {},
series: [
({
{
labels: [
{
key: ({ name: 'service.name' } as unknown) as TelemetryFieldKey,
key: { name: 'service.name' } as unknown as TelemetryFieldKey,
value: 'adservice',
},
],
values: [
({ timestamp: 1000, value: 10 } as unknown) as TimeSeriesValue,
({ timestamp: 2000, value: 12 } as unknown) as TimeSeriesValue,
{ timestamp: 1000, value: 10 } as unknown as TimeSeriesValue,
{ timestamp: 2000, value: 12 } as unknown as TimeSeriesValue,
],
} as unknown) as TimeSeries,
} as unknown as TimeSeries,
],
},
],
@@ -88,10 +88,8 @@ describe('convertV5ResponseToLegacy', () => {
},
]);
const input: SuccessResponse<
MetricRangePayloadV5,
QueryRangeRequestV5
> = makeBaseSuccess({ data: v5Data }, params);
const input: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5> =
makeBaseSuccess({ data: v5Data }, params);
const legendMap = { A: '{{service.name}}' };
const result = convertV5ResponseToLegacy(input, legendMap, false);
@@ -101,7 +99,7 @@ describe('convertV5ResponseToLegacy', () => {
const q = result.payload.data.result[0];
expect(q.queryName).toBe('A');
expect(q.legend).toBe('{{service.name}}');
expect(q.series?.[0]).toEqual(
expect(q.series?.[0]).toStrictEqual(
expect.objectContaining({
labels: { 'service.name': 'adservice' },
values: [
@@ -121,33 +119,33 @@ describe('convertV5ResponseToLegacy', () => {
const scalar: ScalarData = {
columns: [
// group column
({
{
name: 'service.name',
queryName: 'A',
aggregationIndex: 0,
columnType: 'group',
} as unknown) as ScalarData['columns'][number],
} as unknown as ScalarData['columns'][number],
// aggregation 0
({
{
name: '__result_0',
queryName: 'A',
aggregationIndex: 0,
columnType: 'aggregation',
} as unknown) as ScalarData['columns'][number],
} as unknown as ScalarData['columns'][number],
// aggregation 1
({
{
name: '__result_1',
queryName: 'A',
aggregationIndex: 1,
columnType: 'aggregation',
} as unknown) as ScalarData['columns'][number],
} as unknown as ScalarData['columns'][number],
// formula F1
({
{
name: '__result',
queryName: 'F1',
aggregationIndex: 0,
columnType: 'aggregation',
} as unknown) as ScalarData['columns'][number],
} as unknown as ScalarData['columns'][number],
],
data: [['adservice', 606, 1.452, 151.5]],
};
@@ -174,23 +172,21 @@ describe('convertV5ResponseToLegacy', () => {
},
{
type: 'builder_formula',
spec: ({
spec: {
name: 'F1',
expression: 'A * 0.25',
} as unknown) as QueryBuilderFormula,
} as unknown as QueryBuilderFormula,
},
]);
const input: SuccessResponse<
MetricRangePayloadV5,
QueryRangeRequestV5
> = makeBaseSuccess({ data: v5Data }, params);
const input: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5> =
makeBaseSuccess({ data: v5Data }, params);
const legendMap = { A: '{{service.name}}', F1: '' };
const result = convertV5ResponseToLegacy(input, legendMap, false);
expect(result.payload.data.resultType).toBe('scalar');
const [tableEntry] = result.payload.data.result;
expect(tableEntry.table?.columns).toEqual([
expect(tableEntry.table?.columns).toStrictEqual([
{
name: 'service.name',
queryName: 'A',
@@ -206,7 +202,7 @@ describe('convertV5ResponseToLegacy', () => {
},
{ name: 'F1', queryName: 'F1', isValueColumn: true, id: 'F1' },
]);
expect(tableEntry.table?.rows?.[0]).toEqual({
expect(tableEntry.table?.rows?.[0]).toStrictEqual({
data: {
'service.name': 'adservice',
'A.count()': 606,
@@ -254,16 +250,14 @@ describe('convertV5ResponseToLegacy', () => {
},
]);
const input: SuccessResponse<
MetricRangePayloadV5,
QueryRangeRequestV5
> = makeBaseSuccess({ data: v5Data }, params);
const input: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5> =
makeBaseSuccess({ data: v5Data }, params);
const legendMap = { A: '{{service.name}}' };
const result = convertV5ResponseToLegacy(input, legendMap, true);
expect(result.payload.data.resultType).toBe('scalar');
const [tableEntry] = result.payload.data.result;
expect(tableEntry.table?.columns).toEqual([
expect(tableEntry.table?.columns).toStrictEqual([
{
name: 'service.name',
queryName: 'A',
@@ -273,7 +267,7 @@ describe('convertV5ResponseToLegacy', () => {
// Single aggregation: name resolves to legend, id resolves to queryName
{ name: '{{service.name}}', queryName: 'A', isValueColumn: true, id: 'A' },
]);
expect(tableEntry.table?.rows?.[0]).toEqual({
expect(tableEntry.table?.rows?.[0]).toStrictEqual({
data: {
'service.name': 'adservice',
A: 580,

View File

@@ -93,7 +93,7 @@ function convertTimeSeriesData(
labels: series.labels
? Object.fromEntries(
series.labels.map((label: any) => [label.key.name, label.value]),
)
)
: {},
labelsArray: series.labels
? series.labels.map((label: any) => ({ [label.key.name]: label.value }))
@@ -358,16 +358,19 @@ export function convertV5ResponseToLegacy(
const aggregationPerQuery =
(params as QueryRangeRequestV5)?.compositeQuery?.queries
?.filter((query) => query.type === 'builder_query')
.reduce((acc, query) => {
if (
query.type === 'builder_query' &&
'aggregations' in query.spec &&
query.spec.name
) {
acc[query.spec.name] = query.spec.aggregations;
}
return acc;
}, {} as Record<string, any>) || {};
.reduce(
(acc, query) => {
if (
query.type === 'builder_query' &&
'aggregations' in query.spec &&
query.spec.name
) {
acc[query.spec.name] = query.spec.aggregations;
}
return acc;
},
{} as Record<string, any>,
) || {};
// If formatForWeb is true, return as-is (like existing logic)
if (formatForWeb && v5Data?.type === 'scalar') {

View File

@@ -104,7 +104,7 @@ describe('prepareQueryRangePayloadV5', () => {
const result = prepareQueryRangePayloadV5(props);
expect(result).toEqual(
expect(result).toStrictEqual(
expect.objectContaining({
legendMap: { A: 'Legend A', F1: 'Formula Legend' },
queryPayload: expect.objectContaining({
@@ -154,7 +154,10 @@ describe('prepareQueryRangePayloadV5', () => {
);
// Legend map combines builder and formulas
expect(result.legendMap).toEqual({ A: 'Legend A', F1: 'Formula Legend' });
expect(result.legendMap).toStrictEqual({
A: 'Legend A',
F1: 'Formula Legend',
});
const payload: QueryRangePayloadV5 = result.queryPayload;
@@ -166,10 +169,10 @@ describe('prepareQueryRangePayloadV5', () => {
expect(payload.formatOptions?.fillGaps).toBe(true);
// Variables mapped as { key: { value } }
expect(payload.variables).toEqual({
svc: { value: 'api' },
count: { value: 5 },
flag: { value: true },
expect(payload.variables).toStrictEqual({
svc: { value: 'api', type: undefined },
count: { value: 5, type: undefined },
flag: { value: true, type: undefined },
});
// Queries include one builder_query and one builder_formula
@@ -226,7 +229,7 @@ describe('prepareQueryRangePayloadV5', () => {
const result = prepareQueryRangePayloadV5(props);
expect(result).toEqual(
expect(result).toStrictEqual(
expect.objectContaining({
legendMap: { A: 'LP' },
queryPayload: expect.objectContaining({
@@ -255,7 +258,7 @@ describe('prepareQueryRangePayloadV5', () => {
}),
);
expect(result.legendMap).toEqual({ A: 'LP' });
expect(result.legendMap).toStrictEqual({ A: 'LP' });
const payload: QueryRangePayloadV5 = result.queryPayload;
expect(payload.requestType).toBe('time_series');
@@ -296,7 +299,7 @@ describe('prepareQueryRangePayloadV5', () => {
const result = prepareQueryRangePayloadV5(props);
expect(result).toEqual(
expect(result).toStrictEqual(
expect.objectContaining({
legendMap: { Q: 'LC' },
queryPayload: expect.objectContaining({
@@ -324,7 +327,7 @@ describe('prepareQueryRangePayloadV5', () => {
}),
);
expect(result.legendMap).toEqual({ Q: 'LC' });
expect(result.legendMap).toStrictEqual({ Q: 'LC' });
const payload: QueryRangePayloadV5 = result.queryPayload;
expect(payload.requestType).toBe('scalar');
@@ -353,7 +356,7 @@ describe('prepareQueryRangePayloadV5', () => {
const result = prepareQueryRangePayloadV5(props);
expect(result).toEqual(
expect(result).toStrictEqual(
expect.objectContaining({
legendMap: {},
queryPayload: expect.objectContaining({
@@ -397,7 +400,7 @@ describe('prepareQueryRangePayloadV5', () => {
const result = prepareQueryRangePayloadV5(props);
expect(result).toEqual(
expect(result).toStrictEqual(
expect.objectContaining({
legendMap: { A: 'Legend A' },
queryPayload: expect.objectContaining({
@@ -471,7 +474,7 @@ describe('prepareQueryRangePayloadV5', () => {
const result = prepareQueryRangePayloadV5(props);
expect(result).toEqual(
expect(result).toStrictEqual(
expect.objectContaining({
legendMap: { A: 'Legend A' },
queryPayload: expect.objectContaining({
@@ -585,7 +588,7 @@ describe('prepareQueryRangePayloadV5', () => {
const result = prepareQueryRangePayloadV5(props);
expect(result).toEqual(
expect(result).toStrictEqual(
expect.objectContaining({
legendMap: { A: '{{service.name}}' },
queryPayload: expect.objectContaining({
@@ -684,7 +687,7 @@ describe('prepareQueryRangePayloadV5', () => {
const result = prepareQueryRangePayloadV5(props);
expect(result.legendMap).toEqual({ A: 'Legend A' });
expect(result.legendMap).toStrictEqual({ A: 'Legend A' });
expect(result.queryPayload.compositeQuery.queries).toHaveLength(1);
const builderQuery = result.queryPayload.compositeQuery.queries.find(
@@ -694,7 +697,7 @@ describe('prepareQueryRangePayloadV5', () => {
expect(logSpec.name).toBe('A');
expect(logSpec.signal).toBe('logs');
expect(logSpec.filter).toEqual({
expect(logSpec.filter).toStrictEqual({
expression:
"service.name = 'payment-service' AND http.status_code >= 400 AND message contains 'error'",
});
@@ -713,7 +716,7 @@ describe('prepareQueryRangePayloadV5', () => {
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: { expression: 'http.status_code >= 500' },
filters: (undefined as unknown) as IBuilderQuery['filters'],
filters: undefined as unknown as IBuilderQuery['filters'],
}),
],
queryFormulas: [],
@@ -731,7 +734,9 @@ describe('prepareQueryRangePayloadV5', () => {
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.filter).toEqual({ expression: 'http.status_code >= 500' });
expect(logSpec.filter).toStrictEqual({
expression: 'http.status_code >= 500',
});
});
it('derives expression from filters when filter is undefined', () => {
@@ -746,7 +751,7 @@ describe('prepareQueryRangePayloadV5', () => {
queryData: [
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: (undefined as unknown) as IBuilderQuery['filter'],
filter: undefined as unknown as IBuilderQuery['filter'],
filters: {
items: [
{
@@ -775,7 +780,9 @@ describe('prepareQueryRangePayloadV5', () => {
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.filter).toEqual({ expression: "service.name = 'checkout'" });
expect(logSpec.filter).toStrictEqual({
expression: "service.name = 'checkout'",
});
});
it('prefers filter.expression over filters when both are present', () => {
@@ -819,7 +826,9 @@ describe('prepareQueryRangePayloadV5', () => {
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.filter).toEqual({ expression: "service.name = 'frontend'" });
expect(logSpec.filter).toStrictEqual({
expression: "service.name = 'frontend'",
});
});
it('returns empty expression when neither filter nor filters provided', () => {
@@ -834,8 +843,8 @@ describe('prepareQueryRangePayloadV5', () => {
queryData: [
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: (undefined as unknown) as IBuilderQuery['filter'],
filters: (undefined as unknown) as IBuilderQuery['filters'],
filter: undefined as unknown as IBuilderQuery['filter'],
filters: undefined as unknown as IBuilderQuery['filters'],
}),
],
queryFormulas: [],
@@ -853,7 +862,7 @@ describe('prepareQueryRangePayloadV5', () => {
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.filter).toEqual({ expression: '' });
expect(logSpec.filter).toStrictEqual({ expression: '' });
});
it('returns empty expression when filters provided with empty items', () => {
@@ -887,6 +896,6 @@ describe('prepareQueryRangePayloadV5', () => {
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.filter).toEqual({ expression: '' });
expect(logSpec.filter).toStrictEqual({ expression: '' });
});
});

View File

@@ -139,10 +139,9 @@ function createBaseSpec(
requestType: RequestType,
panelType?: PANEL_TYPES,
): BaseBuilderQuery {
const nonEmptySelectColumns = (queryData.selectColumns as (
| BaseAutocompleteData
| TelemetryFieldKey
)[])?.filter((c) => ('key' in c ? c?.key : c?.name));
const nonEmptySelectColumns = (
queryData.selectColumns as (BaseAutocompleteData | TelemetryFieldKey)[]
)?.filter((c) => ('key' in c ? c?.key : c?.name));
return {
stepInterval: queryData?.stepInterval || null,
@@ -160,7 +159,7 @@ function createBaseSpec(
signal: item?.signal,
materialized: item?.materialized,
}),
)
)
: undefined,
limit:
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
@@ -179,52 +178,48 @@ function createBaseSpec(
},
direction: order.order,
}),
)
)
: undefined,
legend: isEmpty(queryData.legend) ? undefined : queryData.legend,
having: isEmpty(queryData.having) ? undefined : (queryData?.having as Having),
functions: isEmpty(queryData.functions)
? undefined
: queryData.functions.map(
(func: QueryFunction): QueryFunction => {
// Normalize function name to handle case sensitivity
const normalizedName = normalizeFunctionName(func?.name);
return {
name: normalizedName as FunctionName,
args: isEmpty(func.namedArgs)
? func.args?.map((arg) => ({
value: arg?.value,
}))
: Object.entries(func?.namedArgs || {}).map(([name, value]) => ({
name,
value,
})),
};
},
),
: queryData.functions.map((func: QueryFunction): QueryFunction => {
// Normalize function name to handle case sensitivity
const normalizedName = normalizeFunctionName(func?.name);
return {
name: normalizedName as FunctionName,
args: isEmpty(func.namedArgs)
? func.args?.map((arg) => ({
value: arg?.value,
}))
: Object.entries(func?.namedArgs || {}).map(([name, value]) => ({
name,
value,
})),
};
}),
selectFields: isEmpty(nonEmptySelectColumns)
? undefined
: nonEmptySelectColumns?.map(
(column: any): TelemetryFieldKey => {
const fieldName = column.name ?? column.key;
const isDeprecated = isDeprecatedField(fieldName);
: nonEmptySelectColumns?.map((column: any): TelemetryFieldKey => {
const fieldName = column.name ?? column.key;
const isDeprecated = isDeprecatedField(fieldName);
const fieldObj: TelemetryFieldKey = {
name: fieldName,
fieldDataType:
column?.fieldDataType ?? (column?.dataType as FieldDataType),
signal: column?.signal ?? undefined,
};
const fieldObj: TelemetryFieldKey = {
name: fieldName,
fieldDataType:
column?.fieldDataType ?? (column?.dataType as FieldDataType),
signal: column?.signal ?? undefined,
};
// Only add fieldContext if the field is NOT deprecated
if (!isDeprecated && fieldName !== 'name') {
fieldObj.fieldContext =
column?.fieldContext ?? (column?.type as FieldContext);
}
// Only add fieldContext if the field is NOT deprecated
if (!isDeprecated && fieldName !== 'name') {
fieldObj.fieldContext =
column?.fieldContext ?? (column?.type as FieldContext);
}
return fieldObj;
},
),
return fieldObj;
}),
};
}
@@ -236,7 +231,8 @@ export function parseAggregations(
const result: { expression: string; alias?: string }[] = [];
// Matches function calls like "count()" or "sum(field)" with optional alias like "as 'alias'"
// Handles quoted ('alias'), dash-separated (field-name), and unquoted values after "as" keyword
const regex = /([a-zA-Z0-9_]+\([^)]*\))(?:\s*as\s+((?:'[^']*'|"[^"]*"|[a-zA-Z0-9_-]+)))?/g;
const regex =
/([a-zA-Z0-9_]+\([^)]*\))(?:\s*as\s+((?:'[^']*'|"[^"]*"|[a-zA-Z0-9_-]+)))?/g;
let match = regex.exec(expression);
while (match !== null) {
const expr = match[1];
@@ -365,10 +361,9 @@ function createTraceOperatorBaseSpec(
requestType: RequestType,
panelType?: PANEL_TYPES,
): BaseBuilderQuery {
const nonEmptySelectColumns = (queryData.selectColumns as (
| BaseAutocompleteData
| TelemetryFieldKey
)[])?.filter((c) => ('key' in c ? c?.key : c?.name));
const nonEmptySelectColumns = (
queryData.selectColumns as (BaseAutocompleteData | TelemetryFieldKey)[]
)?.filter((c) => ('key' in c ? c?.key : c?.name));
const {
stepInterval,
@@ -395,7 +390,7 @@ function createTraceOperatorBaseSpec(
signal: item?.signal,
materialized: item?.materialized,
}),
)
)
: undefined,
limit:
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
@@ -411,7 +406,7 @@ function createTraceOperatorBaseSpec(
},
direction: order.order,
}),
)
)
: undefined,
legend: isEmpty(legend) ? undefined : legend,
having: isEmpty(having) ? undefined : (having as Having),
@@ -425,7 +420,7 @@ function createTraceOperatorBaseSpec(
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
signal: column?.signal ?? undefined,
}),
),
),
};
}
@@ -507,18 +502,22 @@ export function convertClickHouseQueriesToV5(
/**
* Helper function to reduce query arrays to objects
*/
function reduceQueriesToObject(
queryArray: any[],
): { queries: Record<string, any>; legends: Record<string, string> } {
function reduceQueriesToObject(queryArray: any[]): {
queries: Record<string, any>;
legends: Record<string, string>;
} {
const legends: Record<string, string> = {};
const queries = queryArray.reduce((acc, queryItem) => {
if (!queryItem.query) {
const queries = queryArray.reduce(
(acc, queryItem) => {
if (!queryItem.query) {
return acc;
}
acc[queryItem.name] = queryItem;
legends[queryItem.name] = queryItem.legend;
return acc;
}
acc[queryItem.name] = queryItem;
legends[queryItem.name] = queryItem.legend;
return acc;
}, {} as Record<string, any>);
},
{} as Record<string, any>,
);
return { queries, legends };
}
@@ -554,7 +553,7 @@ export const prepareQueryRangePayloadV5 = ({
queryTraceOperator && queryTraceOperator.length > 0
? queryTraceOperator.filter((traceOperator) =>
Boolean(traceOperator.expression.trim()),
)
)
: [];
const currentTraceOperator = mapQueryDataToApi(
@@ -648,15 +647,18 @@ export const prepareQueryRangePayloadV5 = ({
: graphType === PANEL_TYPES.TABLE),
fillGaps: fillGaps || false,
},
variables: Object.entries(variables).reduce((acc, [key, value]) => {
acc[key] = {
value,
type: dynamicVariables
?.find((v) => v.name === key)
?.type?.toLowerCase() as VariableType,
};
return acc;
}, {} as Record<string, VariableItem>),
variables: Object.entries(variables).reduce(
(acc, [key, value]) => {
acc[key] = {
value,
type: dynamicVariables
?.find((v) => v.name === key)
?.type?.toLowerCase() as VariableType,
};
return acc;
},
{} as Record<string, VariableItem>,
),
};
return { legendMap, queryPayload };

View File

@@ -1,26 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/widgets/getQuery';
const getQuery = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.get(
`/query_range?query=${props.query}&start=${props.start}&end=${props.end}&step=${props.step}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getQuery;

File diff suppressed because one or more lines are too long

View File

@@ -1,82 +0,0 @@
function JuiceBoxIcon(): JSX.Element {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.15412 6.3041L3.80207 4.64419C3.80207 4.64419 3.79429 4.48531 3.94429 4.40531C4.09539 4.32532 6.47082 3.35204 6.66192 3.35204C6.85302 3.35204 7.97185 3.56758 8.78625 3.74868C9.60065 3.92978 11.7528 4.39198 11.7628 4.53308C11.7728 4.67419 9.56066 6.93629 9.56066 6.93629L7.15412 6.3041Z"
fill="#C3FECE"
/>
<path
d="M10.2117 5.2686C10.2117 5.2686 8.80626 4.85529 7.43855 4.53419C6.80192 4.3842 4.93091 3.992 4.93091 3.992C4.93091 3.992 5.12979 3.90534 5.27978 3.84201C5.43199 3.77868 5.5531 3.74535 5.5531 3.74535C5.5531 3.74535 6.77303 3.98867 7.67965 4.18199C8.90625 4.44309 10.6228 4.95528 10.6628 5.03639C10.7039 5.11639 10.2117 5.2686 10.2117 5.2686Z"
fill="white"
/>
<path
d="M7.48978 4.87973C7.19091 5.0875 7.48423 5.21305 8.03531 5.34193C8.63861 5.48303 9.09858 5.6908 9.48634 5.46526C9.83632 5.26304 8.9797 5.04306 8.58639 4.95862C8.28641 4.89418 7.72088 4.71974 7.48978 4.87973Z"
fill="#ACB1B2"
/>
<path
d="M8.58521 5.29749C8.58299 5.29749 8.59521 2.86317 8.59521 2.77317C8.59521 2.68318 8.49411 2.60207 8.60521 2.41097C8.71632 2.21987 9.00741 2.23987 9.00741 2.23987C9.00741 2.23987 10.2151 1.88878 10.6973 1.76767C11.1795 1.64657 12.2461 1.36547 12.2461 1.36547L12.3172 1.97544C12.3172 1.97544 11.1995 2.29098 10.6462 2.43208C10.0929 2.57319 9.33851 2.80428 9.33851 2.80428L9.2274 2.90539L9.25962 5.28415C9.25962 5.28415 9.1563 5.3997 8.9252 5.3997C8.70076 5.39859 8.58521 5.29749 8.58521 5.29749Z"
fill="#FFD816"
/>
<path
d="M12.0472 1.67768C12.0561 1.86767 12.1572 1.98544 12.2772 1.98544C12.3972 1.98544 12.4794 1.83211 12.4616 1.63212C12.4427 1.43325 12.3138 1.34214 12.2038 1.3788C12.0939 1.41547 12.0405 1.5388 12.0472 1.67768Z"
fill="#FEB804"
/>
<path
d="M9.22962 2.96094C9.20851 2.96094 9.1874 2.95539 9.16851 2.94206L8.59187 2.55763C8.54076 2.52319 8.52743 2.4543 8.56076 2.4032C8.59521 2.35209 8.66298 2.33764 8.7152 2.37209L9.29184 2.75651C9.34294 2.79095 9.35628 2.85984 9.32295 2.91095C9.30072 2.94317 9.26517 2.96094 9.22962 2.96094Z"
fill="#FEB804"
/>
<path
d="M9.31847 2.86761C9.28514 2.86761 9.25181 2.85206 9.22959 2.82317C9.08182 2.62651 8.91294 2.40319 8.88294 2.36875C8.84516 2.33208 8.83739 2.27209 8.86739 2.22653C8.90183 2.17542 8.9696 2.16098 9.02182 2.19542C9.04182 2.20876 9.05293 2.21653 9.40736 2.68873C9.44402 2.73762 9.41069 2.80761 9.3618 2.84428C9.34181 2.85983 9.34181 2.86761 9.31847 2.86761Z"
fill="#FEB804"
/>
<path
d="M9.18298 3.05427C9.17742 3.05427 9.17187 3.05427 9.1652 3.05316L8.60968 2.96539C8.54968 2.95539 8.50746 2.89872 8.51746 2.83873C8.52746 2.77873 8.58301 2.73651 8.64412 2.74651L9.19965 2.83428C9.25964 2.84428 9.31186 2.85539 9.30186 2.9165C9.29408 2.96983 9.23742 3.05427 9.18298 3.05427Z"
fill="#FEB804"
/>
<path
d="M3.79761 4.63197C3.97426 4.63197 5.39197 5.00417 6.12304 5.13861C6.85411 5.27304 9.06177 5.79524 9.13621 5.80968C9.21066 5.82413 9.19621 6.77963 9.19621 6.77963C9.19621 6.77963 10.2406 12.1793 10.2106 12.4338C10.1806 12.6871 9.49508 14.6414 9.49508 14.6414C9.49508 14.6414 9.09177 14.7014 8.06294 14.4026C7.0341 14.1037 4.31869 13.3582 4.15426 13.1493C3.98982 12.9404 4.09426 10.1806 4.00537 8.48065C3.91427 6.77963 3.79761 4.63197 3.79761 4.63197Z"
fill="#79DD8A"
/>
<path
d="M12.0294 13.2682C12.086 13.0804 11.7705 11.0672 11.7261 9.21728C11.6816 7.36849 11.8405 4.5553 11.7638 4.53308C11.6872 4.51197 9.13621 5.80968 9.13621 5.80968C9.13621 5.80968 9.1251 7.64514 9.15621 9.37616C9.1962 11.616 9.37508 14.5814 9.49396 14.6414C9.61285 14.7014 10.645 14.1237 10.9561 13.9548C11.5072 13.6559 11.9849 13.4182 12.0294 13.2682Z"
fill="#02AB46"
/>
<path
d="M10.204 6.65964L10.2095 5.26638L10.6662 5.04195L10.6795 6.56964L10.4684 6.93962L10.204 6.65964Z"
fill="#DBDFE1"
/>
<path
d="M6.16088 7.1485C5.17427 7.04295 4.49097 7.9229 4.5443 9.06951C4.60763 10.4239 5.4998 11.4216 6.46531 11.6205C7.43081 11.8194 8.36632 11.4005 8.38632 9.91947C8.40854 8.37621 7.05194 7.24294 6.16088 7.1485Z"
fill="#FEFEFD"
/>
<path
d="M6.81861 8.3851C6.81861 8.3851 6.61862 7.71292 6.01754 7.89179C5.41646 8.07067 5.09092 9.37838 6.11087 9.96724C7.09082 10.5328 7.99299 9.49504 7.67745 8.82286C7.3908 8.20956 6.81861 8.3851 6.81861 8.3851Z"
fill="#EF5B44"
/>
<path
d="M6.58968 7.56292C6.52191 7.58737 6.46858 7.86402 6.53968 8.12845C6.59302 8.32622 6.76301 8.59731 6.86189 8.57954C6.94077 8.56509 7.00077 8.254 6.92633 7.9929C6.83967 7.69181 6.66857 7.53404 6.58968 7.56292Z"
fill="#B8CF17"
/>
<path
d="M6.57531 9.12284C6.2931 9.03729 5.97534 10.0072 6.54642 10.6116C7.17639 11.2772 7.9119 10.6905 7.89191 10.5617C7.86302 10.3828 7.36971 10.2539 7.12639 9.9539C6.88307 9.6517 6.73974 9.17284 6.57531 9.12284Z"
fill="#FD8F01"
/>
<path
d="M5.67307 8.9584C5.67307 8.9584 5.72973 8.53398 5.33642 8.55731C4.95644 8.58065 5.04977 9.02951 5.04977 9.02951C5.04977 9.02951 4.73312 9.06729 4.78645 9.41838C4.82645 9.68392 5.10643 9.68059 5.10643 9.68059C5.10643 9.68059 4.79867 9.80169 4.93866 10.1528C5.0631 10.465 5.34975 10.3394 5.34975 10.3394C5.34975 10.3394 5.2442 10.6439 5.49308 10.805C5.70307 10.9405 5.88639 10.8261 5.88639 10.8261C5.88639 10.8261 5.87528 11.0861 6.16526 11.1338C6.51636 11.1916 6.66857 10.7639 6.41858 10.5661C6.22859 10.4161 6.05638 10.5328 6.05638 10.5328C6.05638 10.5328 6.09193 10.4494 6.0586 10.3539C6.03638 10.2917 6.00083 10.2683 6.00083 10.2683C6.00083 10.2683 6.30859 10.2394 6.25859 9.88947C6.2086 9.53837 5.90083 9.5817 5.90083 9.5817C5.90083 9.5817 6.07971 9.43838 6.00861 9.18061C5.93861 8.92174 5.67307 8.9584 5.67307 8.9584Z"
fill="#A281D0"
/>
<path
d="M10.5205 7.04739C10.265 7.04739 9.17503 5.96412 9.16503 5.95301C9.12504 5.90634 9.13059 5.83635 9.17726 5.79635C9.22392 5.75635 9.29281 5.76191 9.33391 5.80746C9.40391 5.88523 10.2372 6.65853 10.4772 6.80741C10.6327 6.5752 11.1383 5.55858 11.5727 4.64085C11.5994 4.5853 11.666 4.56197 11.7205 4.58752C11.776 4.61419 11.7993 4.67974 11.7738 4.73529C10.7027 6.99851 10.5972 7.02851 10.5405 7.04406C10.5339 7.0474 10.5272 7.04739 10.5205 7.04739Z"
fill="#2D802D"
/>
</svg>
);
}
export default JuiceBoxIcon;

View File

@@ -1,38 +0,0 @@
function MagicBallIcon(): JSX.Element {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.2883 12.3928C14.4283 11.0451 15.4015 8.62412 14.396 5.84427C13.9061 4.49101 13.3528 3.73549 12.7084 3.25552C12.3162 2.96331 10.7496 2.2278 8.67524 2.54889C7.15865 2.78443 5.1332 3.74105 3.8166 5.29763C2.54 6.80978 1.5545 8.00416 1.50895 9.1641C1.45006 10.6562 2.57445 11.9384 2.71111 12.1462C2.95443 12.515 4.61434 14.6238 7.73306 14.7316C10.4862 14.826 12.2795 13.5861 13.2883 12.3928Z"
fill="#403D3E"
/>
<path
d="M4.04763 2.43331C2.61104 3.45881 0.996679 5.55647 1.28666 8.49854C1.42777 9.93069 1.77997 10.7995 2.2855 11.4228C2.59326 11.8028 3.92875 12.9328 6.02086 13.0994C8.12964 13.2672 9.73288 12.795 11.4072 11.6306C14.8104 9.26295 14.3093 5.68313 14.1638 5.26649C14.0182 4.84984 12.9283 2.39664 9.93176 1.52446C7.28746 0.755617 5.31867 1.52446 4.04763 2.43331Z"
fill="#5E6367"
/>
<path
d="M6.57189 2.63219C4.99308 2.57553 3.48427 3.81213 3.33872 5.33871C3.19318 6.86419 4.13757 8.02635 5.6086 8.263C7.07964 8.49855 8.78066 7.60526 9.12286 5.73425C9.47618 3.80657 8.07958 2.68663 6.57189 2.63219Z"
fill="white"
/>
<path
d="M7.05971 5.24541C7.05971 5.24541 7.43969 5.16653 7.51524 4.60655C7.58968 4.05547 7.31636 3.5855 6.67862 3.41662C5.98532 3.23329 5.51535 3.61438 5.39313 4.01547C5.22314 4.57322 5.47424 4.83876 5.47424 4.83876C5.47424 4.83876 4.79427 5.00209 4.73983 5.80427C4.68872 6.5609 5.20536 6.96754 5.72422 7.09198C6.3653 7.24642 7.09193 7.07087 7.2697 6.25313C7.41747 5.57984 7.05971 5.24541 7.05971 5.24541Z"
fill="#303030"
/>
<path
d="M5.99081 4.22544C5.92971 4.45543 6.05192 4.67764 6.29191 4.73874C6.55079 4.8043 6.78633 4.71875 6.84966 4.45431C6.90521 4.21988 6.79411 4.01323 6.52079 3.94656C6.29635 3.89101 6.05748 3.97434 5.99081 4.22544Z"
fill="white"
/>
<path
d="M6.18643 5.36871C5.89533 5.27871 5.51091 5.39093 5.4498 5.78202C5.38869 6.17311 5.62313 6.3731 5.92978 6.42865C6.23643 6.4842 6.52641 6.3231 6.58308 6.00978C6.63863 5.69758 6.47641 5.45759 6.18643 5.36871Z"
fill="white"
/>
</svg>
);
}
export default MagicBallIcon;

View File

@@ -1,110 +0,0 @@
function TentIcon(): JSX.Element {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.75393 9.01964L6.72187 8.90409L6.18079 14.0994C6.18079 14.0994 7.01185 14.0416 8.1118 14.0416C9.21174 14.0416 10.1395 14.1194 10.1395 14.1194L9.75393 9.01964Z"
fill="#8A2E08"
/>
<path
d="M3.65538 9.74625L2.9132 9.59182C2.9132 9.59182 2.73099 10.7684 2.56322 11.4973C2.39545 12.2261 2.11658 13.2116 2.11658 13.2116L3.88093 14.0882C3.88093 14.0882 5.47751 14.2282 5.50529 14.1727C5.53306 14.1171 6.70967 11.3851 6.70967 11.3851L6.98965 10.334L3.65538 9.74625Z"
fill="#FF6110"
/>
<path
d="M9.17395 10.3207L12.8715 9.55071L13.0393 9.99847C13.0393 9.99847 13.2826 11.0162 13.4171 11.5673C13.6337 12.4517 13.9126 13.2439 13.9126 13.2439L12.2538 14.2705L10.545 14.2149L9.17395 10.3207Z"
fill="#FF6110"
/>
<path
d="M2.90422 9.15296C2.90422 9.17852 2.92199 9.36406 2.9131 9.49961C2.90422 9.63516 2.87866 9.80182 2.87866 9.80182C2.87866 9.80182 2.97977 9.82404 3.0731 9.88403C3.16642 9.94403 3.35308 10.1729 3.35308 10.1729C3.35308 10.1729 3.71751 10.0385 3.93861 10.1218C4.27859 10.2496 4.42303 10.564 4.42303 10.564C4.42303 10.564 4.77078 10.2407 5.19632 10.3096C5.61185 10.3762 5.77407 10.7684 5.77407 10.7684C5.77407 10.7684 6.08627 10.5162 6.35959 10.5307C6.68291 10.5473 6.80179 10.7851 6.80179 10.7851L7.28621 9.80848L3.68529 9.15407H2.90422V9.15296Z"
fill="#AF0D03"
/>
<path
d="M1.93542 13.6716L2.13097 13.1539C2.13097 13.1539 2.99759 13.5783 3.62534 13.7649C4.25308 13.9516 4.99304 13.9772 4.99304 13.9772C4.99304 13.9772 4.98415 13.6838 4.71194 13.4716C4.43974 13.2594 4.00865 12.9028 4.00865 12.9028L4.59751 12.7061L4.92527 12.575L5.95299 13.7983L5.6219 14.6227C5.6219 14.6227 4.89971 14.716 3.74533 14.4194C2.59095 14.1227 1.93542 13.6716 1.93542 13.6716Z"
fill="#C9C9C9"
/>
<path
d="M9.104 9.63738C9.12067 9.70516 9.11289 10.2407 9.11289 10.2407L9.42732 10.5973C9.42732 10.5973 9.59731 10.3762 9.9884 10.394C10.3795 10.4107 10.5573 10.5896 10.5573 10.5896C10.5573 10.5896 10.795 10.3007 11.1183 10.2418C11.4417 10.1818 11.755 10.3518 11.755 10.3518C11.755 10.3518 11.9761 9.94403 12.2983 9.85071C12.6216 9.75738 13.0394 9.99959 13.0394 9.99959L12.8671 8.95075L9.69953 9.17852L9.104 9.63738Z"
fill="#AF0D03"
/>
<path
d="M13.8782 13.1372L14.0815 13.6549C14.0815 13.6549 13.4615 14.1394 12.6627 14.3605C11.8638 14.5816 10.7528 14.6704 10.7528 14.6704L10.4283 12.8494L11.5238 12.5183L12.1249 12.775C12.1249 12.775 11.5927 13.2227 11.4472 13.4283C11.1705 13.8172 11.2694 14.0638 11.2694 14.0638C11.2694 14.0638 12.1183 13.9872 12.7727 13.7072C13.4282 13.425 13.8782 13.1372 13.8782 13.1372Z"
fill="#C9C9C9"
/>
<path
d="M4.42432 12.8905C4.42432 12.8905 4.80096 13.0594 4.99318 13.1961C5.23094 13.3661 5.46871 13.6205 5.54537 14.0283C5.5987 14.3094 5.62203 14.6227 5.62203 14.6227C5.62203 14.6227 5.88535 14.826 6.4031 13.9605C6.92085 13.095 7.18417 11.8962 7.2275 11.0473C7.26972 10.1985 7.25306 10.0029 7.25306 10.0029L6.48865 11.2595L5.35871 12.2872L4.42432 12.8905Z"
fill="#D92F0A"
/>
<path
d="M7.25966 10.0074C7.25966 10.0074 7.34855 10.7784 6.30971 11.735C5.66419 12.3295 5.03422 12.7383 4.60314 12.9072C4.45537 12.965 4.32315 12.9417 4.22982 12.9405C3.92539 12.9361 3.98206 12.8839 4.15205 12.7972C4.35204 12.695 4.93312 12.4828 6.02084 11.4806C6.89079 10.6784 6.95079 9.87626 6.95079 9.87626L7.25855 9.7096V10.0074H7.25966Z"
fill="#FFFEFF"
/>
<path
d="M10.7949 14.666C10.7949 14.666 10.4127 14.8015 9.835 14.0205C9.25725 13.2394 9.07948 11.855 9.05393 11.4128C9.02837 10.9706 9.03726 10.1385 9.03726 10.1385L10.0994 11.4717C10.0994 11.4717 11.7471 12.7117 11.7216 12.7205C11.696 12.7294 11.1838 13.0516 10.9916 13.375C10.5994 14.0283 10.7949 14.666 10.7949 14.666Z"
fill="#D92F0A"
/>
<path
d="M8.97839 9.77071C8.97839 9.77071 8.96284 10.0618 9.0195 10.2829C9.13839 10.7495 9.54836 11.254 10.0817 11.7273C10.7694 12.3384 11.5683 12.7639 11.6271 12.7806C11.6871 12.7972 12.1116 12.815 12.1193 12.7717C12.1227 12.7572 12.1482 12.6939 12.0093 12.5928C11.7238 12.3872 11.0916 12.025 10.5739 11.6162C10.2083 11.3273 9.73947 10.9684 9.33171 10.2285C9.25727 10.094 9.28393 9.75405 9.28393 9.75405L8.97839 9.77071Z"
fill="white"
/>
<path
d="M3.32974 7.25306C3.32974 7.25306 2.44534 7.36861 2.41756 7.49416C2.38979 7.62082 2.48756 9.28962 2.48756 9.28962C2.48756 9.28962 2.85976 9.22074 3.04753 9.26185C3.2353 9.30296 3.58861 9.53183 3.58861 9.53183L4.18081 9.16741L4.60745 9.7596C4.60745 9.7596 4.90854 9.60405 5.32519 9.64516C5.74183 9.68627 6.02182 9.96736 6.02182 9.96736L6.60401 9.4885L7.25398 9.99959C7.25398 9.99959 7.55063 9.65405 8.10171 9.64405C8.65279 9.63405 8.98611 10.0396 8.98611 10.0396L9.71385 9.40517L10.4316 9.95625C10.4316 9.95625 10.7127 9.72738 11.0349 9.65516C11.3571 9.58183 11.596 9.70738 11.596 9.70738L12.0537 8.89631L12.5737 9.27074C12.5737 9.27074 12.7404 9.07297 12.9792 9.04186C13.2181 9.01075 13.5092 9.00075 13.5092 9.00075C13.5092 9.00075 13.4881 7.87748 13.4881 7.71082C13.4881 7.54416 13.4259 7.27417 13.3525 7.22195C13.2792 7.16973 12.6248 7.02419 12.6248 7.02419L3.32974 7.25306Z"
fill="#D92F0A"
/>
<path
d="M4.4997 7.73859L3.63197 7.93969L3.58752 9.53294C3.58752 9.53294 3.85195 9.45628 4.11639 9.48405C4.38082 9.51183 4.60636 9.76182 4.60636 9.76182C4.60636 9.76182 4.6008 8.72632 4.60969 8.55188C4.61858 8.37745 4.68636 8.04635 4.72302 7.98302C4.75968 7.91858 4.4997 7.73859 4.4997 7.73859Z"
fill="#E1E1E1"
/>
<path
d="M6.78407 8.07747L5.99189 8.36745C5.99189 8.36745 5.97078 9.10074 5.98967 9.39295C6.00744 9.68516 6.02078 9.96959 6.02078 9.96959C6.02078 9.96959 6.38187 9.84959 6.67519 9.86737C6.96739 9.88515 7.25293 10.0018 7.25293 10.0018L7.24182 8.433L6.78407 8.07747Z"
fill="#E1E1E1"
/>
<path
d="M8.95947 8.40634C8.95947 8.40634 8.98725 9.00075 8.99614 9.30184C9.00503 9.60294 8.98503 10.0429 8.98503 10.0429C8.98503 10.0429 9.41612 9.82293 9.75499 9.79515C10.0927 9.76738 10.4305 9.95959 10.4305 9.95959C10.4305 9.95959 10.4038 9.33851 10.3672 8.88187C10.3416 8.56299 10.2483 8.28745 10.2483 8.28745L9.38056 7.82192L8.95947 8.40634Z"
fill="#E1E1E1"
/>
<path
d="M11.5949 9.70961C11.5949 9.70961 11.8827 9.39296 12.1116 9.31963C12.3405 9.2463 12.5727 9.27296 12.5727 9.27296C12.5727 9.27296 12.5593 8.52522 12.5227 8.18635C12.486 7.84859 12.4527 7.65416 12.4527 7.65416L11.3905 7.19974L11.4205 8.03525C11.4205 8.03525 11.5461 8.31412 11.5827 8.79854C11.6183 9.28296 11.5949 9.70961 11.5949 9.70961Z"
fill="#E1E1E1"
/>
<path
d="M8.04625 4.2021L6.7641 4.77318C6.7641 4.77318 5.5986 5.64758 4.67643 6.23088C3.75425 6.81418 3.22428 7.03639 3.11429 7.08195C2.93874 7.15528 2.65431 7.24194 2.53099 7.32638C2.32655 7.46859 2.4121 7.53526 2.44543 7.54637C2.47877 7.55748 3.49871 8.01412 5.30639 8.28188C7.11408 8.54965 9.57172 8.44076 10.8317 8.19633C12.0905 7.9519 13.3526 7.22305 13.3526 7.22305C13.3526 7.22305 13.2471 7.04639 12.5482 6.74308C11.8483 6.43976 11.2539 6.11311 10.2395 5.4487C9.22508 4.78429 8.98065 4.45764 8.98065 4.45764L8.04625 4.2021Z"
fill="#FF6110"
/>
<path
d="M6.96185 5.00653C6.96185 5.00653 5.55081 6.452 3.81312 7.65305C3.62313 7.78416 3.63202 7.9397 3.63202 7.9397C3.63202 7.9397 3.77202 7.99303 4.11644 8.07303C4.46642 8.15414 4.66752 8.17414 4.66752 8.17414C4.66752 8.17414 4.8264 7.99192 5.16528 7.65305C5.77191 7.04642 6.14856 6.56533 6.62298 5.95092C6.9374 5.54316 7.31183 5.0543 7.31183 5.0543L6.96185 5.00653Z"
fill="white"
/>
<path
d="M7.5684 5.19318C7.5684 5.19318 7.0551 6.382 6.81067 6.89531C6.69512 7.13641 6.43735 7.58305 6.20403 7.95636C6.00071 8.2819 5.99182 8.36856 5.99182 8.36856C5.99182 8.36856 6.44846 8.43412 6.66956 8.43412C6.89066 8.43412 7.24064 8.43189 7.24064 8.43189C7.24064 8.43189 7.41619 7.90859 7.60285 6.85975C7.71173 6.24979 7.87061 5.18095 7.87061 5.18095L7.5684 5.19318Z"
fill="white"
/>
<path
d="M8.27954 5.23983C8.27954 5.23983 8.39509 6.38532 8.52397 6.98862C8.69841 7.80525 8.95839 8.40522 8.95839 8.40522C8.95839 8.40522 9.39837 8.38744 9.6428 8.36411C9.88723 8.34077 10.2461 8.28633 10.2461 8.28633C10.2461 8.28633 9.82613 7.6397 9.42059 6.98862C9.15283 6.55754 8.65063 5.55425 8.56953 5.16983C8.50064 4.83874 8.27954 5.23983 8.27954 5.23983Z"
fill="white"
/>
<path
d="M8.90845 5.02984C8.90845 5.06428 9.27732 5.85091 10.1906 6.83752C10.925 7.63081 11.415 8.03857 11.415 8.03857C11.415 8.03857 11.8338 7.90969 12.0216 7.8408C12.2427 7.75969 12.4527 7.65414 12.4527 7.65414C12.4527 7.65414 11.4616 6.9664 10.8783 6.48865C10.295 6.0109 9.49064 5.34649 9.26954 5.04317C9.04844 4.73874 8.90845 5.02984 8.90845 5.02984Z"
fill="white"
/>
<path
d="M8.03411 3.40993C7.78856 3.4277 7.70746 3.84102 7.57969 3.9699C7.45191 4.09767 6.76306 4.7743 6.76306 4.7743C6.76306 4.7743 6.88528 5.41316 8.09855 5.40093C9.20849 5.38871 9.41959 4.86318 9.41959 4.86318C9.41959 4.86318 8.71074 4.10989 8.54741 3.92323C8.38409 3.73547 8.36075 3.3866 8.03411 3.40993Z"
fill="#D92F0A"
/>
<path
d="M8.07516 2.86996C8.07516 2.86996 8.28515 2.8033 8.58402 2.83774C8.88845 2.8733 9.09066 2.97218 9.40731 2.96551C9.89173 2.95663 10.2817 2.72886 10.4217 2.59998C10.5617 2.4711 10.7406 2.26222 10.6472 2.19222C10.5539 2.12223 10.1562 2.22778 9.71174 2.05223C9.31954 1.89779 9.12288 1.49448 8.56624 1.43004C8.08738 1.3756 7.94739 1.57225 7.94739 1.57225L8.07516 2.86996Z"
fill="#FF6110"
/>
<path
d="M7.88843 1.43226V3.6177L8.23841 3.52437L8.20397 1.43226H7.88843Z"
fill="#D92F0A"
/>
</svg>
);
}
export default TentIcon;

View File

@@ -1,41 +0,0 @@
import { CSSProperties } from 'react';
function BarIcon({
fillColor,
}: {
fillColor: CSSProperties['color'];
}): JSX.Element {
return (
<svg
width="30"
height="30"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 2H6C4.89543 2 4 2.89543 4 4V16C4 17.1046 4.89543 18 6 18H8C9.10457 18 10 17.1046 10 16V4C10 2.89543 9.10457 2 8 2Z"
stroke={fillColor}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M18 9H16C14.8954 9 14 9.89543 14 11V16C14 17.1046 14.8954 18 16 18H18C19.1046 18 20 17.1046 20 16V11C20 9.89543 19.1046 9 18 9Z"
stroke={fillColor}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M22 22H2"
stroke={fillColor}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export default BarIcon;

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