Compare commits

..

29 Commits

Author SHA1 Message Date
nityanandagohain
dc63dc0b28 fix: integration with opamp spanmapping 2026-04-24 19:25:32 +05:30
nityanandagohain
fbb47f7fb9 Merge remote-tracking branch 'origin/main' into issue_4361 2026-04-24 11:46:32 +05:30
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
nityanandagohain
7aa69a3265 fix: use mustnewuuid 2026-04-24 00:16:29 +05:30
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
nityanandagohain
6de9ebfcac fix: address comments 2026-04-23 22:14:06 +05:30
nityanandagohain
85c7ce0680 fix: address comments 2026-04-23 18:46:32 +05:30
nityanandagohain
910cbf55cf Merge remote-tracking branch 'origin/main' into issue_4361 2026-04-23 16:24:32 +05:30
nityanandagohain
c30b3870f2 fix: address comments 2026-04-23 16:17:36 +05:30
nityanandagohain
a4da71eb52 fix: more changes 2026-04-22 14:05:56 +05:30
nityanandagohain
f5ed4c90b0 fix: address more comments 2026-04-22 13:54:27 +05:30
nityanandagohain
c03e97221a fix: address comments 2026-04-21 23:31:54 +05:30
nityanandagohain
cd224616f0 fix: address comments 2026-04-21 23:19:49 +05:30
nityanandagohain
50b42aca8c fix: lint issues 2026-04-20 16:09:50 +05:30
nityanandagohain
dbad6cff96 Merge remote-tracking branch 'origin/issue_4361' into issue_4361 2026-04-20 16:09:35 +05:30
nityanandagohain
73a44cee77 Merge remote-tracking branch 'origin/main' into issue_4361 2026-04-20 16:09:12 +05:30
nityanandagohain
b9b7c10af9 fix: remove lint issues 2026-04-20 16:09:09 +05:30
Nityananda Gohain
3d71707f0b Merge branch 'main' into issue_4361 2026-04-20 11:02:57 +05:30
nityanandagohain
f59f611ea2 fix: remove stutters 2026-04-20 11:00:38 +05:30
nityanandagohain
07131f10a9 fix: minor changes 2026-04-20 10:52:17 +05:30
nityanandagohain
d930e77870 fix: cleanup 2026-04-20 10:40:36 +05:30
nityanandagohain
8a9f6bee98 Merge remote-tracking branch 'origin/main' into issue_4361 2026-04-20 09:14:50 +05:30
nityanandagohain
4b06b4546f Merge remote-tracking branch 'origin/issue_4361' into issue_4361 2026-04-12 22:02:12 +05:30
nityanandagohain
526b08a179 feat: 1.types and handler for ai-o11y attribute mapping 2026-04-12 17:32:30 +05:30
nityanandagohain
c7ab610bd8 feat: 1.types and handler for ai-o11y attribute mapping 2026-04-12 17:31:40 +05:30
214 changed files with 7358 additions and 3966 deletions

View File

@@ -39,7 +39,6 @@ jobs:
matrix:
suite:
- alerts
- alertmanager
- callbackauthn
- cloudintegrations
- dashboard

View File

@@ -408,6 +408,16 @@ cloudintegration:
# 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.

View File

@@ -4489,6 +4489,184 @@ components:
type: object
Sigv4SigV4Config:
type: object
SpantypesFieldContext:
enum:
- attribute
- resource
type: string
SpantypesGettableSpanMapperGroups:
properties:
items:
items:
$ref: '#/components/schemas/SpantypesSpanMapperGroup'
type: array
required:
- items
type: object
SpantypesPostableSpanMapper:
properties:
config:
$ref: '#/components/schemas/SpantypesSpanMapperConfig'
enabled:
type: boolean
field_context:
$ref: '#/components/schemas/SpantypesFieldContext'
name:
type: string
required:
- name
- field_context
- config
type: object
SpantypesPostableSpanMapperGroup:
properties:
category:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
enabled:
type: boolean
name:
type: string
required:
- name
- category
- condition
type: object
SpantypesSpanMapper:
properties:
config:
$ref: '#/components/schemas/SpantypesSpanMapperConfig'
createdAt:
format: date-time
type: string
createdBy:
type: string
enabled:
type: boolean
field_context:
$ref: '#/components/schemas/SpantypesFieldContext'
group_id:
type: string
id:
type: string
name:
type: string
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- group_id
- name
- field_context
- config
- enabled
type: object
SpantypesSpanMapperConfig:
properties:
sources:
items:
$ref: '#/components/schemas/SpantypesSpanMapperSource'
nullable: true
type: array
required:
- sources
type: object
SpantypesSpanMapperGroup:
properties:
category:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
createdAt:
format: date-time
type: string
createdBy:
type: string
enabled:
type: boolean
id:
type: string
name:
type: string
orgId:
type: string
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- orgId
- name
- category
- condition
- enabled
type: object
SpantypesSpanMapperGroupCategory:
type: object
SpantypesSpanMapperGroupCondition:
properties:
attributes:
items:
type: string
nullable: true
type: array
resource:
items:
type: string
nullable: true
type: array
required:
- attributes
- resource
type: object
SpantypesSpanMapperOperation:
enum:
- move
- copy
type: string
SpantypesSpanMapperSource:
properties:
context:
$ref: '#/components/schemas/SpantypesFieldContext'
key:
type: string
operation:
$ref: '#/components/schemas/SpantypesSpanMapperOperation'
priority:
type: integer
required:
- key
- context
- operation
- priority
type: object
SpantypesUpdatableSpanMapper:
properties:
config:
$ref: '#/components/schemas/SpantypesSpanMapperConfig'
enabled:
nullable: true
type: boolean
field_context:
$ref: '#/components/schemas/SpantypesFieldContext'
type: object
SpantypesUpdatableSpanMapperGroup:
properties:
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
enabled:
nullable: true
type: boolean
name:
nullable: true
type: string
type: object
TelemetrytypesFieldContext:
enum:
- metric
@@ -4581,6 +4759,140 @@ components:
TimeDuration:
format: int64
type: integer
TracedetailtypesEvent:
properties:
attributeMap:
additionalProperties: {}
type: object
isError:
type: boolean
name:
type: string
timeUnixNano:
minimum: 0
type: integer
type: object
TracedetailtypesGettableWaterfallTrace:
properties:
endTimestampMillis:
minimum: 0
type: integer
hasMissingSpans:
type: boolean
hasMore:
type: boolean
rootServiceEntryPoint:
type: string
rootServiceName:
type: string
serviceNameToTotalDurationMap:
additionalProperties:
minimum: 0
type: integer
nullable: true
type: object
spans:
items:
$ref: '#/components/schemas/TracedetailtypesWaterfallSpan'
nullable: true
type: array
startTimestampMillis:
minimum: 0
type: integer
totalErrorSpansCount:
minimum: 0
type: integer
totalSpansCount:
minimum: 0
type: integer
uncollapsedSpans:
items:
type: string
nullable: true
type: array
type: object
TracedetailtypesPostableWaterfall:
properties:
limit:
minimum: 0
type: integer
selectedSpanId:
type: string
uncollapsedSpans:
items:
type: string
nullable: true
type: array
type: object
TracedetailtypesWaterfallSpan:
properties:
attributes:
additionalProperties: {}
nullable: true
type: object
db_name:
type: string
db_operation:
type: string
duration_nano:
minimum: 0
type: integer
events:
items:
$ref: '#/components/schemas/TracedetailtypesEvent'
nullable: true
type: array
external_http_method:
type: string
external_http_url:
type: string
flags:
minimum: 0
type: integer
has_children:
type: boolean
has_error:
type: boolean
http_host:
type: string
http_method:
type: string
http_url:
type: string
is_remote:
type: string
kind_string:
type: string
level:
minimum: 0
type: integer
name:
type: string
parent_span_id:
type: string
resource:
additionalProperties:
type: string
nullable: true
type: object
response_status_code:
type: string
span_id:
type: string
status_code:
type: integer
status_code_string:
type: string
status_message:
type: string
sub_tree_node_count:
minimum: 0
type: integer
trace_id:
type: string
trace_state:
type: string
type: object
TypesAlertStatus:
properties:
inhibitedBy:
@@ -9097,6 +9409,487 @@ paths:
summary: Updates my service account
tags:
- serviceaccount
/api/v1/span_mapper_groups:
get:
deprecated: false
description: Returns all span attribute mapping groups for the authenticated
org.
operationId: ListSpanMapperGroups
parameters:
- explode: true
in: query
name: category
schema:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
style: deepObject
- in: query
name: enabled
schema:
nullable: true
type: boolean
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesGettableSpanMapperGroups'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List span attribute mapping groups
tags:
- spanmapper
post:
deprecated: false
description: Creates a new span attribute mapping group for the org.
operationId: CreateSpanMapperGroup
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesPostableSpanMapperGroup'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesSpanMapperGroup'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Conflict
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create a span attribute mapping group
tags:
- spanmapper
/api/v1/span_mapper_groups/{groupId}:
delete:
deprecated: false
description: Hard-deletes a mapping group and cascades to all its mappers.
operationId: DeleteSpanMapperGroup
parameters:
- in: path
name: groupId
required: true
schema:
type: string
responses:
"204":
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete a span attribute mapping group
tags:
- spanmapper
patch:
deprecated: false
description: Partially updates an existing mapping group's name, condition,
or enabled state.
operationId: UpdateSpanMapperGroup
parameters:
- in: path
name: groupId
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesUpdatableSpanMapperGroup'
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Update a span attribute mapping group
tags:
- spanmapper
/api/v1/span_mapper_groups/{groupId}/span_mappers:
get:
deprecated: false
description: Returns all mappers belonging to a mapping group.
operationId: ListSpanMappers
parameters:
- in: path
name: groupId
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesGettableSpanMapperGroups'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List span mappers for a group
tags:
- spanmapper
post:
deprecated: false
description: Adds a new mapper to the specified mapping group.
operationId: CreateSpanMapper
parameters:
- in: path
name: groupId
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesPostableSpanMapper'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesSpanMapper'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Conflict
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create a span mapper
tags:
- spanmapper
/api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}:
delete:
deprecated: false
description: Hard-deletes a mapper from a mapping group.
operationId: DeleteSpanMapper
parameters:
- in: path
name: groupId
required: true
schema:
type: string
- in: path
name: mapperId
required: true
schema:
type: string
responses:
"204":
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete a span mapper
tags:
- spanmapper
patch:
deprecated: false
description: Partially updates an existing mapper's field context, config, or
enabled state.
operationId: UpdateSpanMapper
parameters:
- in: path
name: groupId
required: true
schema:
type: string
- in: path
name: mapperId
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesUpdatableSpanMapper'
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Update a span mapper
tags:
- spanmapper
/api/v1/testChannel:
post:
deprecated: true
@@ -15995,6 +16788,76 @@ paths:
summary: Put profile in Zeus for a deployment.
tags:
- zeus
/api/v3/traces/{traceID}/waterfall:
post:
deprecated: false
description: Returns the waterfall view of spans for a given trace ID with tree
structure, metadata, and windowed pagination
operationId: GetWaterfall
parameters:
- in: path
name: traceID
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TracedetailtypesPostableWaterfall'
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/TracedetailtypesGettableWaterfallTrace'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: Get waterfall view for a trace
tags:
- tracedetail
/api/v5/query_range:
post:
deprecated: false

View File

@@ -86,6 +86,28 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq map[string]*o
return provider.openfgaServer.BatchCheck(ctx, tupleReq)
}
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)
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/SigNoz/signoz/pkg/cache/memorycache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/spanmapper/implspanmapper"
"github.com/gorilla/handlers"
@@ -111,9 +112,11 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
}
// initiate agent config handler
spanAttrMappingFeature := implspanmapper.NewSpanAttrMappingFeature(signoz.Modules.SpanMapper)
agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{
Store: signoz.SQLStore,
AgentFeatures: []agentConf.AgentFeature{logParsingPipelineController},
AgentFeatures: []agentConf.AgentFeature{logParsingPipelineController, spanAttrMappingFeature},
})
if err != nil {
return nil, err

View File

@@ -277,23 +277,8 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (int, error) {
annotations := make(ruletypes.Labels, 0, len(r.Annotations().Map()))
for name, value := range r.Annotations().Map() {
// no need to expand custom templating annotations — they get expanded in the notifier layer
if ruletypes.IsCustomTemplatingAnnotation(name) {
annotations = append(annotations, ruletypes.Label{Name: name, Value: value})
continue
}
annotations = append(annotations, ruletypes.Label{Name: name, Value: expand(value)})
}
// Add values to be used in notifier layer for notification templates
annotations = append(annotations, ruletypes.Label{Name: ruletypes.AnnotationValue, Value: value})
annotations = append(annotations, ruletypes.Label{Name: ruletypes.AnnotationThresholdValue, Value: threshold})
annotations = append(annotations, ruletypes.Label{Name: ruletypes.AnnotationCompareOp, Value: smpl.CompareOperator.Literal()})
annotations = append(annotations, ruletypes.Label{Name: ruletypes.AnnotationMatchType, Value: smpl.MatchType.Literal()})
if smpl.IsRecovering {
lb.Set(ruletypes.LabelIsRecovering, "true")
}
if smpl.IsMissing {
lb.Set(ruletypes.AlertNameLabel, "[No data] "+r.Name())
lb.Set(ruletypes.NoDataLabel, "true")

View File

@@ -286,6 +286,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",
{
@@ -597,8 +611,11 @@
"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
}
},
{

View File

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

View File

@@ -2,6 +2,7 @@
<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,14 @@
// 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 +143,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

@@ -0,0 +1,152 @@
/**
* 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

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

@@ -5470,6 +5470,187 @@ export interface Sigv4SigV4ConfigDTO {
[key: string]: unknown;
}
export enum SpantypesFieldContextDTO {
attribute = 'attribute',
resource = 'resource',
}
export interface SpantypesGettableSpanMapperGroupsDTO {
/**
* @type array
*/
items: SpantypesSpanMapperGroupDTO[];
}
export interface SpantypesPostableSpanMapperDTO {
config: SpantypesSpanMapperConfigDTO;
/**
* @type boolean
*/
enabled?: boolean;
field_context: SpantypesFieldContextDTO;
/**
* @type string
*/
name: string;
}
export interface SpantypesPostableSpanMapperGroupDTO {
category: SpantypesSpanMapperGroupCategoryDTO;
condition: SpantypesSpanMapperGroupConditionDTO;
/**
* @type boolean
*/
enabled?: boolean;
/**
* @type string
*/
name: string;
}
export interface SpantypesSpanMapperDTO {
config: SpantypesSpanMapperConfigDTO;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
field_context: SpantypesFieldContextDTO;
/**
* @type string
*/
group_id: string;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export interface SpantypesSpanMapperConfigDTO {
/**
* @type array
* @nullable true
*/
sources: SpantypesSpanMapperSourceDTO[] | null;
}
export interface SpantypesSpanMapperGroupDTO {
category: SpantypesSpanMapperGroupCategoryDTO;
condition: SpantypesSpanMapperGroupConditionDTO;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name: string;
/**
* @type string
*/
orgId: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export interface SpantypesSpanMapperGroupCategoryDTO {
[key: string]: unknown;
}
export interface SpantypesSpanMapperGroupConditionDTO {
/**
* @type array
* @nullable true
*/
attributes: string[] | null;
/**
* @type array
* @nullable true
*/
resource: string[] | null;
}
export enum SpantypesSpanMapperOperationDTO {
move = 'move',
copy = 'copy',
}
export interface SpantypesSpanMapperSourceDTO {
context: SpantypesFieldContextDTO;
/**
* @type string
*/
key: string;
operation: SpantypesSpanMapperOperationDTO;
/**
* @type integer
*/
priority: number;
}
export interface SpantypesUpdatableSpanMapperDTO {
config?: SpantypesSpanMapperConfigDTO;
/**
* @type boolean
* @nullable true
*/
enabled?: boolean | null;
field_context?: SpantypesFieldContextDTO;
}
export interface SpantypesUpdatableSpanMapperGroupDTO {
condition?: SpantypesSpanMapperGroupConditionDTO;
/**
* @type boolean
* @nullable true
*/
enabled?: boolean | null;
/**
* @type string
* @nullable true
*/
name?: string | null;
}
export enum TelemetrytypesFieldContextDTO {
metric = 'metric',
log = 'log',
@@ -5559,6 +5740,237 @@ export interface TelemetrytypesTelemetryFieldValuesDTO {
export type TimeDurationDTO = number;
export type TracedetailtypesEventDTOAttributeMap = { [key: string]: unknown };
export interface TracedetailtypesEventDTO {
/**
* @type object
*/
attributeMap?: TracedetailtypesEventDTOAttributeMap;
/**
* @type boolean
*/
isError?: boolean;
/**
* @type string
*/
name?: string;
/**
* @type integer
* @minimum 0
*/
timeUnixNano?: number;
}
/**
* @nullable
*/
export type TracedetailtypesGettableWaterfallTraceDTOServiceNameToTotalDurationMap =
{ [key: string]: number } | null;
export interface TracedetailtypesGettableWaterfallTraceDTO {
/**
* @type integer
* @minimum 0
*/
endTimestampMillis?: number;
/**
* @type boolean
*/
hasMissingSpans?: boolean;
/**
* @type boolean
*/
hasMore?: boolean;
/**
* @type string
*/
rootServiceEntryPoint?: string;
/**
* @type string
*/
rootServiceName?: string;
/**
* @type object
* @nullable true
*/
serviceNameToTotalDurationMap?: TracedetailtypesGettableWaterfallTraceDTOServiceNameToTotalDurationMap;
/**
* @type array
* @nullable true
*/
spans?: TracedetailtypesWaterfallSpanDTO[] | null;
/**
* @type integer
* @minimum 0
*/
startTimestampMillis?: number;
/**
* @type integer
* @minimum 0
*/
totalErrorSpansCount?: number;
/**
* @type integer
* @minimum 0
*/
totalSpansCount?: number;
/**
* @type array
* @nullable true
*/
uncollapsedSpans?: string[] | null;
}
export interface TracedetailtypesPostableWaterfallDTO {
/**
* @type integer
* @minimum 0
*/
limit?: number;
/**
* @type string
*/
selectedSpanId?: string;
/**
* @type array
* @nullable true
*/
uncollapsedSpans?: string[] | null;
}
/**
* @nullable
*/
export type TracedetailtypesWaterfallSpanDTOAttributes = {
[key: string]: unknown;
} | null;
/**
* @nullable
*/
export type TracedetailtypesWaterfallSpanDTOResource = {
[key: string]: string;
} | null;
export interface TracedetailtypesWaterfallSpanDTO {
/**
* @type object
* @nullable true
*/
attributes?: TracedetailtypesWaterfallSpanDTOAttributes;
/**
* @type string
*/
db_name?: string;
/**
* @type string
*/
db_operation?: string;
/**
* @type integer
* @minimum 0
*/
duration_nano?: number;
/**
* @type array
* @nullable true
*/
events?: TracedetailtypesEventDTO[] | null;
/**
* @type string
*/
external_http_method?: string;
/**
* @type string
*/
external_http_url?: string;
/**
* @type integer
* @minimum 0
*/
flags?: number;
/**
* @type boolean
*/
has_children?: boolean;
/**
* @type boolean
*/
has_error?: boolean;
/**
* @type string
*/
http_host?: string;
/**
* @type string
*/
http_method?: string;
/**
* @type string
*/
http_url?: string;
/**
* @type string
*/
is_remote?: string;
/**
* @type string
*/
kind_string?: string;
/**
* @type integer
* @minimum 0
*/
level?: number;
/**
* @type string
*/
name?: string;
/**
* @type string
*/
parent_span_id?: string;
/**
* @type object
* @nullable true
*/
resource?: TracedetailtypesWaterfallSpanDTOResource;
/**
* @type string
*/
response_status_code?: string;
/**
* @type string
*/
span_id?: string;
/**
* @type integer
*/
status_code?: number;
/**
* @type string
*/
status_code_string?: string;
/**
* @type string
*/
status_message?: string;
/**
* @type integer
* @minimum 0
*/
sub_tree_node_count?: number;
/**
* @type string
*/
trace_id?: string;
/**
* @type string
*/
trace_state?: string;
}
export interface TypesAlertStatusDTO {
/**
* @type array
@@ -6689,6 +7101,71 @@ export type GetMyServiceAccount200 = {
status: string;
};
export type ListSpanMapperGroupsParams = {
/**
* @description undefined
*/
category?: SpantypesSpanMapperGroupCategoryDTO;
/**
* @type boolean
* @nullable true
* @description undefined
*/
enabled?: boolean | null;
};
export type ListSpanMapperGroups200 = {
data: SpantypesGettableSpanMapperGroupsDTO;
/**
* @type string
*/
status: string;
};
export type CreateSpanMapperGroup201 = {
data: SpantypesSpanMapperGroupDTO;
/**
* @type string
*/
status: string;
};
export type DeleteSpanMapperGroupPathParameters = {
groupId: string;
};
export type UpdateSpanMapperGroupPathParameters = {
groupId: string;
};
export type ListSpanMappersPathParameters = {
groupId: string;
};
export type ListSpanMappers200 = {
data: SpantypesGettableSpanMapperGroupsDTO;
/**
* @type string
*/
status: string;
};
export type CreateSpanMapperPathParameters = {
groupId: string;
};
export type CreateSpanMapper201 = {
data: SpantypesSpanMapperDTO;
/**
* @type string
*/
status: string;
};
export type DeleteSpanMapperPathParameters = {
groupId: string;
mapperId: string;
};
export type UpdateSpanMapperPathParameters = {
groupId: string;
mapperId: string;
};
export type ListUsersDeprecated200 = {
/**
* @type array
@@ -7493,6 +7970,17 @@ export type GetHosts200 = {
status: string;
};
export type GetWaterfallPathParameters = {
traceID: string;
};
export type GetWaterfall200 = {
data: TracedetailtypesGettableWaterfallTraceDTO;
/**
* @type string
*/
status: string;
};
export type QueryRangeV5200 = {
data: Querybuildertypesv5QueryRangeResponseDTO;
/**

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

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

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

@@ -12,6 +12,7 @@ import { AppState } from 'store/reducers';
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import { withBasePath } from 'utils/basePath';
export interface NavigateToExplorerProps {
filters: TagFilterItem[];
@@ -133,7 +134,7 @@ export function useNavigateToExplorer(): (
QueryParams.compositeQuery
}=${JSONCompositeQuery}`;
window.open(newExplorerPath, sameTab ? '_self' : '_blank');
window.open(withBasePath(newExplorerPath), sameTab ? '_self' : '_blank');
},
[
prepareQuery,

View File

@@ -9,6 +9,7 @@ import { CreditCard, MessageSquareText, X } from 'lucide-react';
import { SuccessResponseV2 } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
export default function ChatSupportGateway(): JSX.Element {
const { notifications } = useNotifications();
@@ -54,7 +55,7 @@ export default function ChatSupportGateway(): JSX.Element {
});
updateCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
};

View File

@@ -28,6 +28,7 @@ import { useAppContext } from 'providers/App/App';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { useTimezone } from 'providers/Timezone';
import APIError from 'types/api/error';
import { getAbsoluteUrl } from 'utils/basePath';
import { toAPIError } from 'utils/errorUtils';
import DeleteMemberDialog from './DeleteMemberDialog';
@@ -381,7 +382,7 @@ function EditMemberDrawer({
pathParams: { id: member.id },
});
if (response?.data?.token) {
const link = `${window.location.origin}/password-reset?token=${response.data.token}`;
const link = getAbsoluteUrl(`/password-reset?token=${response.data.token}`);
setResetLink(link);
setResetLinkExpiresAt(
response.data.expiresAt

View File

@@ -13,6 +13,7 @@ import GetMinMax from 'lib/getMinMax';
import { Check, Info, Link2 } from 'lucide-react';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getAbsoluteUrl } from 'utils/basePath';
const routesToBeSharedWithTime = [
ROUTES.LOGS_EXPLORER,
@@ -80,17 +81,13 @@ function ShareURLModal(): JSX.Element {
urlQuery.delete(QueryParams.relativeTime);
currentUrl = `${window.location.origin}${
location.pathname
}?${urlQuery.toString()}`;
currentUrl = getAbsoluteUrl(`${location.pathname}?${urlQuery.toString()}`);
} else {
urlQuery.delete(QueryParams.startTime);
urlQuery.delete(QueryParams.endTime);
urlQuery.set(QueryParams.relativeTime, selectedTime);
currentUrl = `${window.location.origin}${
location.pathname
}?${urlQuery.toString()}`;
currentUrl = getAbsoluteUrl(`${location.pathname}?${urlQuery.toString()}`);
}
}

View File

@@ -17,6 +17,7 @@ import { useErrorModal } from 'providers/ErrorModalProvider';
import APIError from 'types/api/error';
import { ROLES } from 'types/roles';
import { EMAIL_REGEX } from 'utils/app';
import { getBaseUrl } from 'utils/basePath';
import { popupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
@@ -191,7 +192,7 @@ function InviteMembersModal({
email: row.email.trim(),
name: '',
role: row.role as ROLES,
frontendBaseUrl: window.location.origin,
frontendBaseUrl: getBaseUrl(),
});
} else {
await inviteUsers({
@@ -199,7 +200,7 @@ function InviteMembersModal({
email: row.email.trim(),
name: '',
role: row.role,
frontendBaseUrl: window.location.origin,
frontendBaseUrl: getBaseUrl(),
})),
});
}

View File

@@ -14,6 +14,7 @@ import { useAppContext } from 'providers/App/App';
import { SuccessResponseV2 } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
import './LaunchChatSupport.styles.scss';
@@ -154,7 +155,7 @@ function LaunchChatSupport({
});
updateCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
};

View File

@@ -1,6 +1,7 @@
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { ArrowUpRight } from 'lucide-react';
import { openInNewTab } from 'utils/navigation';
import './LearnMore.styles.scss';
@@ -14,7 +15,7 @@ function LearnMore({ text, url, onClick }: LearnMoreProps): JSX.Element {
const handleClick = (): void => {
onClick?.();
if (url) {
window.open(url, '_blank');
openInNewTab(url);
}
};
return (

View File

@@ -20,6 +20,7 @@ import {
KAFKA_SETUP_DOC_LINK,
MessagingQueueHealthCheckService,
} from 'pages/MessagingQueues/MessagingQueuesUtils';
import { openInNewTab } from 'utils/navigation';
import { v4 as uuid } from 'uuid';
import './MessagingQueueHealthCheck.styles.scss';
@@ -76,7 +77,7 @@ function ErrorTitleAndKey({
if (isCloudUserVal && !!link) {
history.push(link);
} else {
window.open(KAFKA_SETUP_DOC_LINK, '_blank');
openInNewTab(KAFKA_SETUP_DOC_LINK);
}
};
return {

View File

@@ -1,10 +1,13 @@
import getSessionStorageApi from 'api/browser/sessionstorage/get';
import removeSessionStorageApi from 'api/browser/sessionstorage/remove';
import setSessionStorageApi from 'api/browser/sessionstorage/set';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
export const PREVIOUS_QUERY_KEY = 'previousQuery';
function getPreviousQueryFromStore(): Record<string, IBuilderQuery> {
try {
const raw = sessionStorage.getItem(PREVIOUS_QUERY_KEY);
const raw = getSessionStorageApi(PREVIOUS_QUERY_KEY);
if (!raw) {
return {};
}
@@ -17,7 +20,7 @@ function getPreviousQueryFromStore(): Record<string, IBuilderQuery> {
function writePreviousQueryToStore(store: Record<string, IBuilderQuery>): void {
try {
sessionStorage.setItem(PREVIOUS_QUERY_KEY, JSON.stringify(store));
setSessionStorageApi(PREVIOUS_QUERY_KEY, JSON.stringify(store));
} catch {
// ignore quota or serialization errors
}
@@ -63,7 +66,7 @@ export const removeKeyFromPreviousQuery = (key: string): void => {
export const clearPreviousQuery = (): void => {
try {
sessionStorage.removeItem(PREVIOUS_QUERY_KEY);
removeSessionStorageApi(PREVIOUS_QUERY_KEY);
} catch {
// no-op
}

View File

@@ -108,8 +108,7 @@ function DynamicColumnTable({
// Update URL with new page number while preserving other params
urlQuery.set('page', page.toString());
const newUrl = `${window.location.pathname}?${urlQuery.toString()}`;
safeNavigate(newUrl);
safeNavigate({ search: `?${urlQuery.toString()}` });
// Call original pagination handler if provided
if (pagination?.onChange && !!pageSize) {

View File

@@ -1,3 +1,6 @@
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { DynamicColumnsKey } from './contants';
import {
GetNewColumnDataFunction,
@@ -12,7 +15,7 @@ export const getVisibleColumns: GetVisibleColumnsFunction = ({
}) => {
let columnVisibilityData: { [key: string]: boolean };
try {
const storedData = localStorage.getItem(tablesource);
const storedData = getLocalStorageKey(tablesource);
if (typeof storedData === 'string' && dynamicColumns) {
columnVisibilityData = JSON.parse(storedData);
return dynamicColumns.filter((column) => {
@@ -28,7 +31,7 @@ export const getVisibleColumns: GetVisibleColumnsFunction = ({
initialColumnVisibility[key] = false;
});
localStorage.setItem(tablesource, JSON.stringify(initialColumnVisibility));
setLocalStorageKey(tablesource, JSON.stringify(initialColumnVisibility));
} catch (error) {
console.error(error);
}
@@ -42,14 +45,14 @@ export const setVisibleColumns = ({
dynamicColumns,
}: SetVisibleColumnsProps): void => {
try {
const storedData = localStorage.getItem(tablesource);
const storedData = getLocalStorageKey(tablesource);
if (typeof storedData === 'string' && dynamicColumns) {
const columnVisibilityData = JSON.parse(storedData);
const { key } = dynamicColumns[index];
if (key) {
columnVisibilityData[key] = checked;
}
localStorage.setItem(tablesource, JSON.stringify(columnVisibilityData));
setLocalStorageKey(tablesource, JSON.stringify(columnVisibilityData));
}
} catch (error) {
console.error(error);

View File

@@ -9,6 +9,7 @@ import {
} from 'container/ApiMonitoring/utils';
import { UnfoldVertical } from 'lucide-react';
import { SuccessResponse } from 'types/api';
import { openInNewTab } from 'utils/navigation';
import emptyStateUrl from '@/assets/Icons/emptyState.svg';
@@ -94,20 +95,14 @@ function DependentServices({
}}
onRow={(record): { onClick: () => void; className: string } => ({
onClick: (): void => {
const url = new URL(
`/services/${
record.serviceData.serviceName &&
record.serviceData.serviceName !== '-'
? record.serviceData.serviceName
: ''
}`,
window.location.origin,
);
const serviceName =
record.serviceData.serviceName && record.serviceData.serviceName !== '-'
? record.serviceData.serviceName
: '';
const urlQuery = new URLSearchParams();
urlQuery.set(QueryParams.startTime, timeRange.startTime.toString());
urlQuery.set(QueryParams.endTime, timeRange.endTime.toString());
url.search = urlQuery.toString();
window.open(url.toString(), '_blank');
openInNewTab(`/services/${serviceName}?${urlQuery.toString()}`);
},
className: 'clickable-row',
})}

View File

@@ -73,6 +73,7 @@ import {
import { UserPreference } from 'types/api/preferences/preference';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { getBaseUrl } from 'utils/basePath';
import { showErrorNotification } from 'utils/error';
import { eventEmitter } from 'utils/getEventEmitter';
import {
@@ -461,7 +462,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const handleFailedPayment = useCallback((): void => {
manageCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
}, [manageCreditCard]);

View File

@@ -31,6 +31,7 @@ import { isEmpty, pick } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
import { SuccessResponseV2 } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import { getBaseUrl } from 'utils/basePath';
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
import { BillingUsageGraph } from './BillingUsageGraph/BillingUsageGraph';
@@ -324,7 +325,7 @@ export default function BillingContainer(): JSX.Element {
});
updateCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
} else {
logEvent('Billing : Manage Billing', {
@@ -333,7 +334,7 @@ export default function BillingContainer(): JSX.Element {
});
manageCreditCard({
url: window.location.origin,
url: getBaseUrl(),
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@@ -7,6 +7,7 @@ import { FeatureKeys } from 'constants/features';
import { useAppContext } from 'providers/App/App';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { isModifierKeyPressed } from 'utils/app';
import { openInNewTab } from 'utils/navigation';
import { getOptionList } from './config';
import { AlertTypeCard, SelectTypeContainer } from './styles';
@@ -55,7 +56,7 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
page: 'New alert data source selection page',
});
window.open(url, '_blank');
openInNewTab(url);
}
const renderOptions = useMemo(
() => (

View File

@@ -14,6 +14,7 @@ import { IUser } from 'providers/App/types';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { USER_ROLES } from 'types/roles';
import { openInNewTab } from 'utils/navigation';
import { ROUTING_POLICIES_ROUTE } from './constants';
import { RoutingPolicyBannerProps } from './types';
@@ -387,7 +388,7 @@ export function NotificationChannelsNotFoundContent({
style={{ padding: '0 4px' }}
type="link"
onClick={(): void => {
window.open(ROUTES.CHANNELS_NEW, '_blank');
openInNewTab(ROUTES.CHANNELS_NEW);
}}
>
here.

View File

@@ -46,6 +46,7 @@ function DomainUpdateToast({
className="custom-domain-toast-visit-btn"
suffix={<ExternalLink size={12} />}
onClick={(): void => {
// oxlint-disable-next-line signoz/no-raw-absolute-path
window.open(url, '_blank', 'noopener,noreferrer');
}}
>
@@ -74,10 +75,8 @@ export default function CustomDomainSettings(): JSX.Element {
const [isPollingEnabled, setIsPollingEnabled] = useState(false);
const [hosts, setHosts] = useState<ZeustypesHostDTO[] | null>(null);
const [
updateDomainError,
setUpdateDomainError,
] = useState<AxiosError<RenderErrorResponseDTO> | null>(null);
const [updateDomainError, setUpdateDomainError] =
useState<AxiosError<RenderErrorResponseDTO> | null>(null);
const [customDomainSubdomain, setCustomDomainSubdomain] = useState<
string | undefined
@@ -90,10 +89,8 @@ export default function CustomDomainSettings(): JSX.Element {
refetch: refetchHosts,
} = useGetHosts();
const {
mutate: updateSubDomain,
isLoading: isLoadingUpdateCustomDomain,
} = usePutHost<AxiosError<RenderErrorResponseDTO>>();
const { mutate: updateSubDomain, isLoading: isLoadingUpdateCustomDomain } =
usePutHost<AxiosError<RenderErrorResponseDTO>>();
const stripProtocol = (url: string): string => url?.split('://')[1] ?? url;
@@ -137,7 +134,7 @@ export default function CustomDomainSettings(): JSX.Element {
{
onSuccess: () => {
setIsPollingEnabled(true);
refetchHosts();
void refetchHosts();
setIsEditModalOpen(false);
setCustomDomainSubdomain(subdomain);
const newUrl = `https://${subdomain}.${dnsSuffix}`;

View File

@@ -15,6 +15,8 @@ import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
import { PublicDashboardMetaProps } from 'types/api/dashboard/public/getMeta';
import APIError from 'types/api/error';
import { USER_ROLES } from 'types/roles';
import { getAbsoluteUrl } from 'utils/basePath';
import { openInNewTab } from 'utils/navigation';
import './PublicDashboard.styles.scss';
@@ -212,7 +214,7 @@ function PublicDashboardSetting(): JSX.Element {
try {
setCopyPublicDashboardURL(
`${window.location.origin}${publicDashboardResponse?.data?.publicPath}`,
getAbsoluteUrl(publicDashboardResponse?.data?.publicPath ?? ''),
);
toast.success('Copied Public Dashboard URL successfully');
} catch (error) {
@@ -221,7 +223,7 @@ function PublicDashboardSetting(): JSX.Element {
};
const publicDashboardURL = useMemo(
() => `${window.location.origin}${publicDashboardResponse?.data?.publicPath}`,
() => getAbsoluteUrl(publicDashboardResponse?.data?.publicPath ?? ''),
[publicDashboardResponse],
);
@@ -294,7 +296,7 @@ function PublicDashboardSetting(): JSX.Element {
icon={<ExternalLink size={12} />}
onClick={(): void => {
if (publicDashboardURL) {
window.open(publicDashboardURL, '_blank');
openInNewTab(publicDashboardURL);
}
}}
/>

View File

@@ -1,5 +1,6 @@
import { useCallback, useRef } from 'react';
import { Button } from 'antd';
import getSessionStorageApi from 'api/browser/sessionstorage/get';
import ROUTES from 'constants/routes';
import { DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY } from 'hooks/dashboard/useDashboardsListQueryParams';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
@@ -26,7 +27,7 @@ function DashboardBreadcrumbs(): JSX.Element {
const { title = '', image = Base64Icons[0] } = selectedData || {};
const goToListPage = useCallback(() => {
const dashboardsListQueryParamsString = sessionStorage.getItem(
const dashboardsListQueryParamsString = getSessionStorageApi(
DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY,
);

View File

@@ -1,3 +1,6 @@
import getLocalStorageKey from 'api/browser/localstorage/get';
import removeLocalStorageKey from 'api/browser/localstorage/remove';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import { GraphVisibilityState, SeriesVisibilityItem } from '../types';
@@ -12,7 +15,7 @@ export function getStoredSeriesVisibility(
widgetId: string,
): SeriesVisibilityItem[] | null {
try {
const storedData = localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
const storedData = getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
if (!storedData) {
return null;
@@ -29,7 +32,7 @@ export function getStoredSeriesVisibility(
} catch (error) {
if (error instanceof SyntaxError) {
// If the stored data is malformed, remove it
localStorage.removeItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
removeLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
}
// Silently handle parsing errors - fall back to default visibility
return null;
@@ -42,7 +45,7 @@ export function updateSeriesVisibilityToLocalStorage(
): void {
let visibilityStates: GraphVisibilityState[] = [];
try {
const storedData = localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
const storedData = getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
visibilityStates = JSON.parse(storedData || '[]');
} catch (error) {
if (error instanceof SyntaxError) {
@@ -63,7 +66,7 @@ export function updateSeriesVisibilityToLocalStorage(
];
}
localStorage.setItem(
setLocalStorageKey(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
JSON.stringify(visibilityStates),
);

View File

@@ -22,6 +22,8 @@ import {
Tooltip,
Typography,
} from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import { TelemetryFieldKey } from 'api/v5/v5';
import axios from 'axios';
@@ -472,7 +474,7 @@ function ExplorerOptions({
value: string;
}): void => {
// Retrieve stored views from local storage
const storedViews = localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
const storedViews = getLocalStorageKey(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
// Initialize or parse the stored views
const updatedViews: PreservedViewsInLocalStorage = storedViews
@@ -486,7 +488,7 @@ function ExplorerOptions({
};
// Save the updated views back to local storage
localStorage.setItem(
setLocalStorageKey(
PRESERVED_VIEW_LOCAL_STORAGE_KEY,
JSON.stringify(updatedViews),
);
@@ -537,7 +539,7 @@ function ExplorerOptions({
const removeCurrentViewFromLocalStorage = (): void => {
// Retrieve stored views from local storage
const storedViews = localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
const storedViews = getLocalStorageKey(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
if (storedViews) {
// Parse the stored views
@@ -547,7 +549,7 @@ function ExplorerOptions({
delete parsedViews[PRESERVED_VIEW_TYPE];
// Update local storage with the modified views
localStorage.setItem(
setLocalStorageKey(
PRESERVED_VIEW_LOCAL_STORAGE_KEY,
JSON.stringify(parsedViews),
);
@@ -672,7 +674,7 @@ function ExplorerOptions({
}
const parsedPreservedView = JSON.parse(
localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY) || '{}',
getLocalStorageKey(PRESERVED_VIEW_LOCAL_STORAGE_KEY) || '{}',
);
const preservedView = parsedPreservedView[PRESERVED_VIEW_TYPE] || {};

View File

@@ -1,4 +1,6 @@
import { Color } from '@signozhq/design-tokens';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { showErrorNotification } from 'components/ExplorerCard/utils';
import { LOCALSTORAGE } from 'constants/localStorage';
import { QueryParams } from 'constants/query';
@@ -71,7 +73,7 @@ export const generateRGBAFromHex = (hex: string, opacity: number): string =>
export const getExplorerToolBarVisibility = (dataSource: string): boolean => {
try {
const showExplorerToolbar = localStorage.getItem(
const showExplorerToolbar = getLocalStorageKey(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
);
if (showExplorerToolbar === null) {
@@ -84,7 +86,7 @@ export const getExplorerToolBarVisibility = (dataSource: string): boolean => {
[DataSource.TRACES]: true,
[DataSource.LOGS]: true,
};
localStorage.setItem(
setLocalStorageKey(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
JSON.stringify(parsedShowExplorerToolbar),
);
@@ -103,13 +105,13 @@ export const setExplorerToolBarVisibility = (
dataSource: string,
): void => {
try {
const showExplorerToolbar = localStorage.getItem(
const showExplorerToolbar = getLocalStorageKey(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
);
if (showExplorerToolbar) {
const parsedShowExplorerToolbar = JSON.parse(showExplorerToolbar);
parsedShowExplorerToolbar[dataSource] = value;
localStorage.setItem(
setLocalStorageKey(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
JSON.stringify(parsedShowExplorerToolbar),
);

View File

@@ -9,6 +9,7 @@ import ROUTES from 'constants/routes';
import history from 'lib/history';
import APIError from 'types/api/error';
import { OrgSessionContext } from 'types/api/v2/sessions/context/get';
import { getBaseUrl } from 'utils/basePath';
import tvUrl from '@/assets/svgs/tv.svg';
@@ -104,7 +105,7 @@ function ForgotPassword({
data: {
email: values.email,
orgId: currentOrgId,
frontendBaseURL: window.location.origin,
frontendBaseURL: getBaseUrl(),
},
});
}, [form, forgotPasswordMutate, initialOrgId, hasMultipleOrgs]);

View File

@@ -15,6 +15,7 @@ import { AlertDef, Labels } from 'types/api/alerts/def';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
import { openInNewTab } from 'utils/navigation';
import { popupContainer } from 'utils/selectPopupContainer';
import ChannelSelect from './ChannelSelect';
@@ -87,7 +88,7 @@ function BasicInfo({
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
ruleId: isNewRule ? 0 : alertDef?.id,
});
window.open(ROUTES.CHANNELS_NEW, '_blank');
openInNewTab(ROUTES.CHANNELS_NEW);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const hasLoggedEvent = useRef(false);

View File

@@ -138,8 +138,8 @@ function ChartPreview({
if (startTime && endTime && startTime !== endTime) {
dispatch(
UpdateTimeInterval('custom', [
Number.parseInt(getTimeString(startTime), 10),
Number.parseInt(getTimeString(endTime), 10),
parseInt(getTimeString(startTime), 10),
parseInt(getTimeString(endTime), 10),
]),
);
}

View File

@@ -55,6 +55,7 @@ import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import { isModifierKeyPressed } from 'utils/app';
import { compositeQueryToQueryEnvelope } from 'utils/compositeQueryToQueryEnvelope';
import { openInNewTab } from 'utils/navigation';
import BasicInfo from './BasicInfo';
import ChartPreview from './ChartPreview';
@@ -369,7 +370,7 @@ function FormAlertRules({
// onQueryCategoryChange handles changes to query category
// in state as well as sets additional defaults
const onQueryCategoryChange = (val: EQueryType): void => {
const element = document.querySelector('#top');
const element = document.getElementById('top');
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
@@ -777,7 +778,7 @@ function FormAlertRules({
queryType: currentQuery.queryType,
link: url,
});
window.open(url, '_blank');
openInNewTab(url);
}
}

View File

@@ -1,3 +1,5 @@
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import getLabelName from 'lib/getLabelName';
import { QueryData } from 'types/api/widgets/getQuery';
@@ -100,7 +102,7 @@ export const saveLegendEntriesToLocalStorage = ({
try {
existingEntries = JSON.parse(
localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) || '[]',
getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) || '[]',
);
} catch (error) {
console.error('Error parsing LEGEND_GRAPH from local storage', error);
@@ -115,7 +117,7 @@ export const saveLegendEntriesToLocalStorage = ({
}
try {
localStorage.setItem(
setLocalStorageKey(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
JSON.stringify(existingEntries),
);

View File

@@ -1,5 +1,6 @@
/* eslint-disable sonarjs/cognitive-complexity */
import type { NotificationInstance } from 'antd/es/notification/interface';
import getLocalStorageKey from 'api/browser/localstorage/get';
import { NavigateToExplorerProps } from 'components/CeleryTask/useNavigateToExplorer';
import { LOCALSTORAGE } from 'constants/localStorage';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -44,8 +45,8 @@ export const getLocalStorageGraphVisibilityState = ({
],
};
if (localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = localStorage.getItem(
if (getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = getLocalStorageKey(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
);
let legendFromLocalStore: {
@@ -94,8 +95,8 @@ export const getGraphVisibilityStateOnDataChange = ({
graphVisibilityStates: Array(options.series.length).fill(true),
legendEntry: showAllDataSet(options),
};
if (localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = localStorage.getItem(
if (getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = getLocalStorageKey(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
);
let legendFromLocalStore: {

View File

@@ -10,6 +10,7 @@ import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { Dashboard } from 'types/api/dashboard/getAll';
import { USER_ROLES } from 'types/roles';
import { openInNewTab } from 'utils/navigation';
import dialsUrl from '@/assets/Icons/dials.svg';
@@ -114,7 +115,7 @@ export default function Dashboards({
dashboardName: dashboard.data.title,
});
if (event.metaKey || event.ctrlKey) {
window.open(getLink(), '_blank');
openInNewTab(getLink());
} else {
safeNavigate(getLink());
}

View File

@@ -9,6 +9,7 @@ import { Link2 } from 'lucide-react';
import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { LicensePlatform } from 'types/api/licensesV3/getActive';
import { openInNewTab } from 'utils/navigation';
import containerPlusUrl from '@/assets/Icons/container-plus.svg';
import helloWaveUrl from '@/assets/Icons/hello-wave.svg';
@@ -51,7 +52,7 @@ function DataSourceInfo({
if (activeLicense && activeLicense.platform === LicensePlatform.CLOUD) {
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
} else {
window?.open(DOCS_LINKS.ADD_DATA_SOURCE, '_blank', 'noopener noreferrer');
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
}
};

View File

@@ -8,6 +8,7 @@ import { ArrowRight, ArrowRightToLine, BookOpenText } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { LicensePlatform } from 'types/api/licensesV3/getActive';
import { USER_ROLES } from 'types/roles';
import { openInNewTab } from 'utils/navigation';
import './HomeChecklist.styles.scss';
@@ -99,11 +100,7 @@ function HomeChecklist({
) {
history.push(item.toRoute || '');
} else {
window?.open(
item.docsLink || '',
'_blank',
'noopener noreferrer',
);
openInNewTab(item.docsLink || '');
}
}}
>
@@ -119,7 +116,7 @@ function HomeChecklist({
step: item.id,
});
window?.open(item.docsLink, '_blank', 'noopener noreferrer');
openInNewTab(item.docsLink ?? '');
}}
>
<BookOpenText size={16} />

View File

@@ -31,6 +31,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import { Tags } from 'types/reducer/trace';
import { USER_ROLES } from 'types/roles';
import { isModifierKeyPressed } from 'utils/app';
import { openInNewTab } from 'utils/navigation';
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
@@ -79,11 +80,7 @@ const EmptyState = memo(
) {
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
} else {
window?.open(
DOCS_LINKS.ADD_DATA_SOURCE,
'_blank',
'noopener noreferrer',
);
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
}
}}
>

View File

@@ -17,6 +17,7 @@ import { ServicesList } from 'types/api/metrics/getService';
import { GlobalReducer } from 'types/reducer/globalTime';
import { USER_ROLES } from 'types/roles';
import { isModifierKeyPressed } from 'utils/app';
import { openInNewTab } from 'utils/navigation';
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
@@ -133,11 +134,7 @@ export default function ServiceTraces({
) {
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
} else {
window?.open(
DOCS_LINKS.ADD_DATA_SOURCE,
'_blank',
'noopener noreferrer',
);
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
}
}}
>

View File

@@ -51,6 +51,7 @@ import {
LogsAggregatorOperator,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
import { openInNewTab } from 'utils/navigation';
import { v4 as uuidv4 } from 'uuid';
import { filterDuplicateFilters } from '../commonUtils';
@@ -569,10 +570,7 @@ function K8sBaseDetails<T>({
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
window.open(
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
'_blank',
);
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
} else if (selectedView === VIEW_TYPES.TRACES) {
const compositeQuery = {
...initialQueryState,
@@ -591,10 +589,7 @@ function K8sBaseDetails<T>({
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
window.open(
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
'_blank',
);
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
}
};

View File

@@ -4,6 +4,7 @@ import {
CloudintegrationtypesServiceDTO,
} from 'api/generated/services/sigNoz.schemas';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { withBasePath } from 'utils/basePath';
import './ServiceDashboards.styles.scss';
@@ -44,7 +45,11 @@ function ServiceDashboards({
return;
}
if (event.metaKey || event.ctrlKey) {
window.open(dashboardUrl, '_blank', 'noopener,noreferrer');
window.open(
withBasePath(dashboardUrl),
'_blank',
'noopener,noreferrer',
);
return;
}
safeNavigate(dashboardUrl);
@@ -54,7 +59,11 @@ function ServiceDashboards({
return;
}
if (event.button === 1) {
window.open(dashboardUrl, '_blank', 'noopener,noreferrer');
window.open(
withBasePath(dashboardUrl),
'_blank',
'noopener,noreferrer',
);
}
}}
onKeyDown={(event): void => {

View File

@@ -1,5 +1,6 @@
import { ArrowRightOutlined } from '@ant-design/icons';
import { Typography } from 'antd';
import { openInNewTab } from 'utils/navigation';
interface AlertInfoCardProps {
header: string;
@@ -19,7 +20,7 @@ function AlertInfoCard({
className="alert-info-card"
onClick={(): void => {
onClick();
window.open(link, '_blank');
openInNewTab(link);
}}
>
<div className="alert-card-text">

View File

@@ -1,5 +1,6 @@
import { ArrowRightOutlined, PlayCircleFilled } from '@ant-design/icons';
import { Flex, Typography } from 'antd';
import { openInNewTab } from 'utils/navigation';
interface InfoLinkTextProps {
infoText: string;
@@ -20,7 +21,7 @@ function InfoLinkText({
<Flex
onClick={(): void => {
onClick();
window.open(link, '_blank');
openInNewTab(link);
}}
className="info-link-container"
>

View File

@@ -28,6 +28,8 @@ import {
Typography,
} from 'antd';
import type { TableProps } from 'antd/lib';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import createDashboard from 'api/v1/dashboards/create';
import { AxiosError } from 'axios';
@@ -83,6 +85,8 @@ import {
} from 'types/api/dashboard/getAll';
import APIError from 'types/api/error';
import { isModifierKeyPressed } from 'utils/app';
import { getAbsoluteUrl } from 'utils/basePath';
import { openInNewTab } from 'utils/navigation';
import awwSnapUrl from '@/assets/Icons/awwSnap.svg';
import dashboardsUrl from '@/assets/Icons/dashboards.svg';
@@ -145,7 +149,7 @@ function DashboardsList(): JSX.Element {
);
const getLocalStorageDynamicColumns = (): DashboardDynamicColumns => {
const dashboardDynamicColumnsString = localStorage.getItem('dashboard');
const dashboardDynamicColumnsString = getLocalStorageKey('dashboard');
let dashboardDynamicColumns: DashboardDynamicColumns = {
createdAt: true,
createdBy: true,
@@ -159,7 +163,7 @@ function DashboardsList(): JSX.Element {
);
if (isEmpty(tempDashboardDynamicColumns)) {
localStorage.setItem('dashboard', JSON.stringify(dashboardDynamicColumns));
setLocalStorageKey('dashboard', JSON.stringify(dashboardDynamicColumns));
} else {
dashboardDynamicColumns = { ...tempDashboardDynamicColumns };
}
@@ -167,7 +171,7 @@ function DashboardsList(): JSX.Element {
console.error(error);
}
} else {
localStorage.setItem('dashboard', JSON.stringify(dashboardDynamicColumns));
setLocalStorageKey('dashboard', JSON.stringify(dashboardDynamicColumns));
}
return dashboardDynamicColumns;
@@ -181,7 +185,7 @@ function DashboardsList(): JSX.Element {
visibleColumns: DashboardDynamicColumns,
): void {
try {
localStorage.setItem('dashboard', JSON.stringify(visibleColumns));
setLocalStorageKey('dashboard', JSON.stringify(visibleColumns));
} catch (error) {
console.error(error);
}
@@ -457,7 +461,7 @@ function DashboardsList(): JSX.Element {
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
window.open(getLink(), '_blank');
openInNewTab(getLink());
}}
>
Open in New Tab
@@ -469,7 +473,7 @@ function DashboardsList(): JSX.Element {
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
setCopy(`${window.location.origin}${getLink()}`);
setCopy(getAbsoluteUrl(getLink()));
}}
>
Copy Link

View File

@@ -1,6 +1,7 @@
import { LockFilled } from '@ant-design/icons';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { openInNewTab } from 'utils/navigation';
import { Data } from '../DashboardsList';
import { TableLinkText } from './styles';
@@ -12,7 +13,7 @@ function Name(name: Data['name'], data: Data): JSX.Element {
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
if (event.metaKey || event.ctrlKey) {
window.open(getLink(), '_blank');
openInNewTab(getLink());
} else {
history.push(getLink());
}

View File

@@ -17,6 +17,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
import { ILog } from 'types/api/logs/log';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { withBasePath } from 'utils/basePath';
import { useContextLogData } from './useContextLogData';
@@ -116,7 +117,7 @@ function ContextLogRenderer({
);
const link = `${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`;
window.open(link, '_blank', 'noopener,noreferrer');
window.open(withBasePath(link), '_blank', 'noopener,noreferrer');
},
[query, urlQuery],
);

View File

@@ -34,6 +34,7 @@ import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { openInNewTab } from 'utils/navigation';
import { ActionItemProps } from './ActionItem';
import FieldRenderer from './FieldRenderer';
@@ -191,7 +192,7 @@ function TableView({
if (event.ctrlKey || event.metaKey) {
// open the trace in new tab
window.open(route, '_blank');
openInNewTab(route);
} else {
history.push(route);
}

View File

@@ -60,10 +60,8 @@ function Login(): JSX.Element {
const [sessionsContext, setSessionsContext] = useState<SessionsContext>();
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const [sessionsOrgId, setSessionsOrgId] = useState<string>('');
const [
sessionsContextLoading,
setIsLoadingSessionsContext,
] = useState<boolean>(false);
const [sessionsContextLoading, setIsLoadingSessionsContext] =
useState<boolean>(false);
const [form] = Form.useForm<FormValues>();
const [errorMessage, setErrorMessage] = useState<APIError>();
@@ -213,6 +211,7 @@ function Login(): JSX.Element {
if (isCallbackAuthN) {
const url = form.getFieldValue('url');
// oxlint-disable-next-line signoz/no-raw-absolute-path
window.location.href = url;
}
} catch (error) {
@@ -328,7 +327,6 @@ function Login(): JSX.Element {
data-testid="email"
required
placeholder="e.g. john@signoz.io"
autoFocus
disabled={versionLoading}
className="login-form-input"
onPressEnter={onNextHandler}

View File

@@ -34,6 +34,7 @@ import ROUTES from 'constants/routes';
import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useDragColumns from 'hooks/useDragColumns';
import { getAbsoluteUrl } from 'utils/basePath';
import { infinityDefaultStyles } from '../InfinityTableView/config';
import { TanStackTableStyled } from '../InfinityTableView/styles';
@@ -239,7 +240,7 @@ const TanStackTableView = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
urlQuery.delete(QueryParams.activeLogId);
urlQuery.delete(QueryParams.relativeTime);
urlQuery.set(QueryParams.activeLogId, `"${logId}"`);
const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`;
const link = getAbsoluteUrl(`${pathname}?${urlQuery.toString()}`);
setCopy(link);
toast.success('Copied to clipboard', { position: 'top-right' });

View File

@@ -1,5 +1,6 @@
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { withBasePath } from 'utils/basePath';
import { TopOperationList } from './TopOperationsTable';
import { NavigateToTraceProps } from './types';
@@ -37,7 +38,7 @@ export const navigateToTrace = ({
}=${JSONCompositeQuery}`;
if (openInNewTab) {
window.open(newTraceExplorerPath, '_blank');
window.open(withBasePath(newTraceExplorerPath), '_blank');
} else {
safeNavigate(newTraceExplorerPath);
}

View File

@@ -280,7 +280,7 @@ function Explorer(): JSX.Element {
[],
);
const [warning, setWarning] = useState<Warning | undefined>();
const [warning, setWarning] = useState<Warning | undefined>(undefined);
const oneChartPerQueryDisabledTooltip = useMemo(() => {
if (splitedQueries.length <= 1) {
@@ -292,7 +292,7 @@ function Explorer(): JSX.Element {
if (disableOneChartPerQuery) {
return 'One chart per query cannot be disabled for multiple queries with different units.';
}
return;
return undefined;
}, [disableOneChartPerQuery, splitedQueries.length, units.length]);
// Show the y axis unit selector if -

View File

@@ -221,7 +221,7 @@ function Inspect({
);
}
if (inspectMetricsTimeSeries.length === 0) {
if (!inspectMetricsTimeSeries.length) {
return renderFallback(
'inspect-metrics-empty',
<Empty description="No time series found for this metric to inspect." />,

View File

@@ -256,10 +256,10 @@ export function useInspectMetrics(
const valuesMap = new Map<number, number>();
series.values.forEach(({ timestamp, value }) => {
valuesMap.set(timestamp, Number.parseFloat(value));
valuesMap.set(timestamp, parseFloat(value));
});
return timestamps.map((timestamp) => valuesMap.get(timestamp) ?? Number.NaN);
return timestamps.map((timestamp) => valuesMap.get(timestamp) ?? NaN);
});
const rawData = [timestamps, ...timeseriesArray];
@@ -273,7 +273,7 @@ export function useInspectMetrics(
labels.add(label);
});
});
return [...labels];
return Array.from(labels);
}, [inspectMetricsData]);
const reset = useCallback(() => {

View File

@@ -9,6 +9,7 @@ import {
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { Bell, Grid } from 'lucide-react';
import { openInNewTab } from 'utils/navigation';
import { pluralize } from 'utils/pluralize';
import { DashboardsAndAlertsPopoverProps } from './types';
@@ -67,9 +68,8 @@ function DashboardsAndAlertsPopover({
<Typography.Link
key={alert.alertId}
onClick={(): void => {
window.open(
openInNewTab(
`${ROUTES.ALERT_OVERVIEW}?${QueryParams.ruleId}=${alert.alertId}`,
'_blank',
);
}}
className="dashboards-popover-content-item"
@@ -90,11 +90,10 @@ function DashboardsAndAlertsPopover({
<Typography.Link
key={dashboard.dashboardId}
onClick={(): void => {
window.open(
openInNewTab(
generatePath(ROUTES.DASHBOARD, {
dashboardId: dashboard.dashboardId,
}),
'_blank',
);
}}
className="dashboards-popover-content-item"

View File

@@ -18,6 +18,7 @@ import {
import useContextVariables from 'hooks/dashboard/useContextVariables';
import { Plus, Trash2 } from 'lucide-react';
import { ContextLinkProps, Widgets } from 'types/api/dashboard/getAll';
import { getBaseUrl } from 'utils/basePath';
import VariablesDropdown from './VariablesDropdown';
@@ -84,7 +85,7 @@ function UpdateContextLinks({
);
// Function to get current domain
const getCurrentDomain = (): string => window.location.origin;
const getCurrentDomain = (): string => getBaseUrl();
// Function to handle variable selection from dropdown
const handleVariableSelect = (

View File

@@ -6,6 +6,7 @@ import history from 'lib/history';
import { ArrowUpRight } from 'lucide-react';
import { DataSource } from 'types/common/queryBuilder';
import DOCLINKS from 'utils/docLinks';
import { openInNewTab } from 'utils/navigation';
import eyesEmojiUrl from '@/assets/Images/eyesEmoji.svg';
@@ -42,11 +43,11 @@ export default function NoLogs({
}
history.push(link);
} else if (dataSource === 'traces') {
window.open(DOCLINKS.TRACES_EXPLORER_EMPTY_STATE, '_blank');
openInNewTab(DOCLINKS.TRACES_EXPLORER_EMPTY_STATE);
} else if (dataSource === DataSource.METRICS) {
window.open(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE, '_blank');
openInNewTab(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE);
} else {
window.open(`${DOCLINKS.USER_GUIDE}${dataSource}/`, '_blank');
openInNewTab(`${DOCLINKS.USER_GUIDE}${dataSource}/`);
}
};
return (

View File

@@ -16,6 +16,7 @@ import {
Trash2,
} from 'lucide-react';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
import { v4 as uuid } from 'uuid';
import { OnboardingQuestionHeader } from '../OnboardingQuestionHeader';
@@ -58,7 +59,7 @@ function InviteTeamMembers({
email: '',
role: '',
name: '',
frontendBaseUrl: window.location.origin,
frontendBaseUrl: getBaseUrl(),
id: '',
};

View File

@@ -8,6 +8,7 @@ import { DOCS_BASE_URL } from 'constants/app';
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
import { useNotifications } from 'hooks/useNotifications';
import { ArrowUpRight, Copy, Info, Key, TriangleAlert } from 'lucide-react';
import { withBasePath } from 'utils/basePath';
import './IngestionDetails.styles.scss';
@@ -215,7 +216,7 @@ export default function OnboardingIngestionDetails(): JSX.Element {
</a>
. To create a new one,{' '}
<a
href="/settings/ingestion-settings"
href={withBasePath('/settings/ingestion-settings')}
target="_blank"
className="learn-more"
rel="noreferrer"

View File

@@ -8,6 +8,7 @@ import { useNotifications } from 'hooks/useNotifications';
import { cloneDeep, debounce, isEmpty } from 'lodash-es';
import { ArrowRight, CheckCircle, Plus, TriangleAlert, X } from 'lucide-react';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
import { v4 as uuid } from 'uuid';
import './InviteTeamMembers.styles.scss';
@@ -56,7 +57,7 @@ function InviteTeamMembers({
email: '',
role: 'EDITOR',
name: '',
frontendBaseUrl: window.location.origin,
frontendBaseUrl: getBaseUrl(),
id: '',
};

View File

@@ -17,6 +17,7 @@ import ErrorContent from 'components/ErrorModal/components/ErrorContent';
import CopyToClipboard from 'periscope/components/CopyToClipboard';
import { useErrorModal } from 'providers/ErrorModalProvider';
import APIError from 'types/api/error';
import { getAbsoluteUrl } from 'utils/basePath';
import CreateEdit from './CreateEdit/CreateEdit';
import SSOEnforcementToggle from './SSOEnforcementToggle';
@@ -144,7 +145,7 @@ function AuthDomain(): JSX.Element {
return <span className="auth-domain-list-na">N/A</span>;
}
const href = `${window.location.origin}/${relayPath}`;
const href = getAbsoluteUrl(`/${relayPath}`);
return <CopyToClipboard textToCopy={href} />;
},
},

View File

@@ -4,6 +4,7 @@ import { Button, Form, FormInstance, Modal } from 'antd';
import sendInvite from 'api/v1/invite/create';
import { useNotifications } from 'hooks/useNotifications';
import APIError from 'types/api/error';
import { getBaseUrl } from 'utils/basePath';
import InviteTeamMembers from '../InviteTeamMembers';
import { InviteMemberFormValues } from '../utils';
@@ -40,7 +41,7 @@ function InviteUserModal(props: InviteUserModalProps): JSX.Element {
email: member.email,
name: member?.name,
role: member.role,
frontendBaseUrl: window.location.origin,
frontendBaseUrl: getBaseUrl(),
});
notifications.success({

View File

@@ -18,7 +18,7 @@ describe('RunQueryBtn', () => {
);
});
it('renders run state and triggers on click', async () => {
test('renders run state and triggers on click', async () => {
const user = userEvent.setup();
const onRun = jest.fn();
const onCancel = jest.fn();
@@ -35,7 +35,7 @@ describe('RunQueryBtn', () => {
expect(onRun).toHaveBeenCalledTimes(1);
});
it('shows cancel state and calls handleCancelQuery', async () => {
test('shows cancel state and calls handleCancelQuery', async () => {
const user = userEvent.setup();
const onRun = jest.fn();
const onCancel = jest.fn();
@@ -51,19 +51,19 @@ describe('RunQueryBtn', () => {
expect(onCancel).toHaveBeenCalledTimes(1);
});
it('disabled when disabled prop is true', () => {
test('disabled when disabled prop is true', () => {
render(<RunQueryBtn disabled />);
expect(screen.getByRole('button', { name: /run query/i })).toBeDisabled();
});
it('disabled when no props provided', () => {
test('disabled when no props provided', () => {
render(<RunQueryBtn />);
expect(
screen.getByRole('button', { name: /run query/i }),
).toBeInTheDocument();
});
it('shows Command + CornerDownLeft on mac', () => {
test('shows Command + CornerDownLeft on mac', () => {
const { container } = render(
<RunQueryBtn
onStageRunQuery={jest.fn()}
@@ -77,7 +77,7 @@ describe('RunQueryBtn', () => {
).toBeInTheDocument();
});
it('shows ChevronUp + CornerDownLeft on non-mac', () => {
test('shows ChevronUp + CornerDownLeft on non-mac', () => {
(getUserOperatingSystem as jest.Mock).mockReturnValue(
UserOperatingSystem.WINDOWS,
);
@@ -95,7 +95,7 @@ describe('RunQueryBtn', () => {
).toBeInTheDocument();
});
it('renders custom label when provided', () => {
test('renders custom label when provided', () => {
render(
<RunQueryBtn
onStageRunQuery={jest.fn()}

View File

@@ -14,6 +14,7 @@ import ContextMenu from 'periscope/components/ContextMenu';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
import { ContextLinksData } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { openInNewTab } from 'utils/navigation';
import { ContextMenuItem } from './contextConfig';
import { getDataLinks } from './dataLinksUtils';
@@ -115,7 +116,7 @@ const useBaseAggregateOptions = ({
key={id}
icon={<LinkOutlined />}
onClick={(): void => {
window.open(url, '_blank');
openInNewTab(url);
onClose?.();
}}
>

View File

@@ -14,6 +14,7 @@ import { ModalTitle } from 'container/PipelinePage/PipelineListsView/styles';
import { Check, Loader, X } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { USER_ROLES } from 'types/roles';
import { openInNewTab } from 'utils/navigation';
import { INITIAL_ROUTING_POLICY_DETAILS_FORM_STATE } from './constants';
import {
@@ -76,7 +77,7 @@ function RoutingPolicyDetails({
style={{ padding: '0 4px' }}
type="link"
onClick={(): void => {
window.open(ROUTES.CHANNELS_NEW, '_blank');
openInNewTab(ROUTES.CHANNELS_NEW);
}}
>
here.

View File

@@ -818,7 +818,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
);
if (item && !('type' in item) && item.isExternal && item.url) {
window.open(item.url, '_blank');
openInNewTab(item.url);
}
const event = (info as SidebarItem & { domEvent?: MouseEvent }).domEvent;

View File

@@ -28,6 +28,7 @@ import {
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { openInNewTab } from 'utils/navigation';
import { v4 as uuid } from 'uuid';
import noDataUrl from '@/assets/Icons/no-data.svg';
@@ -143,7 +144,7 @@ function SpanLogs({
const url = `${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`;
window.open(url, '_blank');
openInNewTab(url);
},
[
isLogSpanRelated,

View File

@@ -17,6 +17,7 @@ import { BarChart2, Compass, X } from 'lucide-react';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Span } from 'types/api/trace/getTraceV2';
import { DataSource, LogsAggregatorOperator } from 'types/common/queryBuilder';
import { getAbsoluteUrl } from 'utils/basePath';
import { RelatedSignalsViews } from '../constants';
import SpanLogs from '../SpanLogs/SpanLogs';
@@ -158,9 +159,7 @@ function SpanRelatedSignals({
searchParams.set(QueryParams.endTime, endTimeMs.toString());
window.open(
`${window.location.origin}${
ROUTES.LOGS_EXPLORER
}?${searchParams.toString()}`,
getAbsoluteUrl(`${ROUTES.LOGS_EXPLORER}?${searchParams.toString()}`),
'_blank',
'noopener,noreferrer',
);

View File

@@ -31,6 +31,7 @@ import {
UPDATE_SPANS_AGGREGATE_PAGE_SIZE,
} from 'types/actions/trace';
import { TraceReducer } from 'types/reducer/trace';
import { openInNewTab } from 'utils/navigation';
import { v4 } from 'uuid';
dayjs.extend(duration);
@@ -214,7 +215,7 @@ function TraceTable(): JSX.Element {
event.preventDefault();
event.stopPropagation();
if (event.metaKey || event.ctrlKey) {
window.open(getLink(record), '_blank');
openInNewTab(getLink(record));
} else {
history.push(getLink(record));
}

View File

@@ -28,6 +28,7 @@ import { useTimezone } from 'providers/Timezone';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { openInNewTab } from 'utils/navigation';
import './TracesTableComponent.styles.scss';
@@ -86,7 +87,7 @@ function TracesTableComponent({
event.preventDefault();
event.stopPropagation();
if (event.metaKey || event.ctrlKey) {
window.open(getTraceLink(record), '_blank');
openInNewTab(getTraceLink(record));
} else {
history.push(getTraceLink(record));
}

View File

@@ -1,4 +1,5 @@
import { useState } from 'react';
import setSessionStorageApi from 'api/browser/sessionstorage/set';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import isEqual from 'lodash-es/isEqual';
@@ -61,7 +62,7 @@ function useDashboardsListQueryParams(): {
const queryParamsString = params.toString();
sessionStorage.setItem(
setSessionStorageApi(
DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY,
queryParamsString,
);

View File

@@ -72,7 +72,7 @@ export function useIntegrationModal({
ModalStateEnum.FORM,
);
const [isLoading, setIsLoading] = useState(false);
const [accountId, setAccountId] = useState<string | undefined>(undefined);
const [accountId, setAccountId] = useState<string | undefined>();
const [activeView, setActiveView] = useState<ActiveViewEnum>(
ActiveViewEnum.FORM,
);
@@ -80,7 +80,7 @@ export function useIntegrationModal({
const [includeAllRegions, setIncludeAllRegions] = useState(false);
const [selectedDeploymentRegion, setSelectedDeploymentRegion] = useState<
string | undefined
>(undefined);
>();
const allRegions = useMemo(
() => regions.flatMap((r) => r.subRegions.map((sr) => sr.name)),
[],
@@ -108,7 +108,7 @@ export function useIntegrationModal({
const handleConnectionSuccess = useCallback(
(payload: { cloudAccountId: string; status?: unknown }): void => {
logEvent('AWS Integration: Account connected', {
void logEvent('AWS Integration: Account connected', {
cloudAccountId: payload.cloudAccountId,
status: payload.status,
});
@@ -126,7 +126,7 @@ export function useIntegrationModal({
const handleConnectionTimeout = useCallback(
(payload: { id?: string }): void => {
setModalState(ModalStateEnum.ERROR);
logEvent('AWS Integration: Account connection attempt timed out', {
void logEvent('AWS Integration: Account connection attempt timed out', {
id: payload.id,
});
},
@@ -140,19 +140,17 @@ export function useIntegrationModal({
const { mutate: generateUrl, isLoading: isGeneratingUrl } = useCreateAccount();
const handleError = useAxiosError();
const {
data: connectionParams,
isLoading: isConnectionParamsLoading,
} = useGetConnectionCredentials<GetConnectionCredentialsQueryResult>(
{
cloudProvider: INTEGRATION_TYPES.AWS,
},
{
query: {
onError: handleError,
const { data: connectionParams, isLoading: isConnectionParamsLoading } =
useGetConnectionCredentials<GetConnectionCredentialsQueryResult>(
{
cloudProvider: INTEGRATION_TYPES.AWS,
},
},
);
{
query: {
onError: handleError,
},
},
);
const handleGenerateUrl = useCallback(
(payload: CloudintegrationtypesPostableAccountDTO): void => {
@@ -167,12 +165,13 @@ export function useIntegrationModal({
const connectionUrl =
response.data.connectionArtifact.aws?.connectionUrl ?? '';
logEvent(
void logEvent(
'AWS Integration: Account connection attempt redirected to AWS',
{
id: accountId,
},
);
// oxlint-disable-next-line signoz/no-raw-absolute-path -- connectionUrl is an external AWS console URL, not an internal path
window.open(connectionUrl, '_blank');
setModalState(ModalStateEnum.WAITING);
setAccountId(accountId);

View File

@@ -17,6 +17,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getAbsoluteUrl } from 'utils/basePath';
import { HIGHLIGHTED_DELAY } from './configs';
import { UseCopyLogLink } from './types';
@@ -60,7 +61,7 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
urlQuery.set(QueryParams.startTime, minTime?.toString() || '');
urlQuery.set(QueryParams.endTime, maxTime?.toString() || '');
const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`;
const link = getAbsoluteUrl(`${pathname}?${urlQuery.toString()}`);
setCopy(link);

View File

@@ -21,6 +21,7 @@ import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
import { withBasePath } from 'utils/basePath';
import { getGraphType } from 'utils/getGraphType';
const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
@@ -95,7 +96,7 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
const url = `${ROUTES.ALERTS_NEW}?${params.toString()}`;
window.open(url, '_blank', 'noreferrer');
window.open(withBasePath(url), '_blank', 'noreferrer');
},
onError: () => {
notifications.error({

View File

@@ -115,8 +115,8 @@ export const useGetQueryRange: UseGetQueryRange = (
const updatedQuery = updateBarStepInterval(
requestData.query,
requestData.start ? requestData.start * 1e3 : Number.parseInt(start, 10) * 1e3,
requestData.end ? requestData.end * 1e3 : Number.parseInt(end, 10) * 1e3,
requestData.start ? requestData.start * 1e3 : parseInt(start, 10) * 1e3,
requestData.end ? requestData.end * 1e3 : parseInt(end, 10) * 1e3,
);
return {

View File

@@ -4,6 +4,7 @@ import { useCopyToClipboard } from 'react-use';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
import { Span } from 'types/api/trace/getTraceV2';
import { getAbsoluteUrl } from 'utils/basePath';
export const useCopySpanLink = (
span?: Span,
@@ -28,7 +29,7 @@ export const useCopySpanLink = (
urlQuery.set('spanId', span?.spanId);
}
const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`;
const link = getAbsoluteUrl(`${pathname}?${urlQuery.toString()}`);
setCopy(link);
notifications.success({

View File

@@ -1,34 +1,20 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import getLocalStorageApi from 'api/browser/localstorage/get';
import removeLocalStorageApi from 'api/browser/localstorage/remove';
import setLocalStorageApi from 'api/browser/localstorage/set';
/**
* A React hook for interacting with localStorage.
* It allows getting, setting, and removing items from localStorage.
*
* @template T The type of the value to be stored.
* @param {string} key The localStorage key.
* @param {T | (() => T)} defaultValue The default value to use if no value is found in localStorage,
* @returns {[T, (value: T | ((prevState: T) => T)) => void, () => void]}
* A tuple containing:
* - The current value from state (and localStorage).
* - A function to set the value (updates state and localStorage).
* - A function to remove the value from localStorage and reset state to defaultValue.
*/
export function useLocalStorage<T>(
key: string,
defaultValue: T | (() => T),
): [T, (value: T | ((prevState: T) => T)) => void, () => void] {
// Stabilize the defaultValue to prevent unnecessary re-renders
const defaultValueRef = useRef<T | (() => T)>(defaultValue);
// Update the ref if defaultValue changes (for cases where it's intentionally dynamic)
useEffect(() => {
if (defaultValueRef.current !== defaultValue) {
defaultValueRef.current = defaultValue;
}
}, [defaultValue]);
// This function resolves the defaultValue if it's a function,
// and handles potential errors during localStorage access or JSON parsing.
const readValueFromStorage = useCallback((): T => {
const resolveddefaultValue =
defaultValueRef.current instanceof Function
@@ -36,33 +22,25 @@ export function useLocalStorage<T>(
: defaultValueRef.current;
try {
const item = window.localStorage.getItem(key);
// If item exists, parse it, otherwise return the resolved default value.
const item = getLocalStorageApi(key);
if (item) {
return JSON.parse(item) as T;
}
} catch (error) {
// Log error and fall back to default value if reading/parsing fails.
console.warn(`Error reading localStorage key "${key}":`, error);
}
return resolveddefaultValue;
}, [key]);
// Initialize state by reading from localStorage.
const [storedValue, setStoredValue] = useState<T>(readValueFromStorage);
// This function updates both localStorage and the React state.
const setValue = useCallback(
(value: T | ((prevState: T) => T)) => {
try {
// If a function is passed to setValue, it receives the latest value from storage.
const latestValueFromStorage = readValueFromStorage();
const valueToStore =
value instanceof Function ? value(latestValueFromStorage) : value;
// Save to localStorage.
window.localStorage.setItem(key, JSON.stringify(valueToStore));
// Update React state.
setLocalStorageApi(key, JSON.stringify(valueToStore));
setStoredValue(valueToStore);
} catch (error) {
console.warn(`Error setting localStorage key "${key}":`, error);
@@ -71,11 +49,9 @@ export function useLocalStorage<T>(
[key, readValueFromStorage],
);
// This function removes the item from localStorage and resets the React state.
const removeValue = useCallback(() => {
try {
window.localStorage.removeItem(key);
// Reset state to the (potentially resolved) defaultValue.
removeLocalStorageApi(key);
setStoredValue(
defaultValueRef.current instanceof Function
? (defaultValueRef.current as () => T)()
@@ -86,12 +62,9 @@ export function useLocalStorage<T>(
}
}, [key]);
// useEffect to update the storedValue if the key changes,
// or if the defaultValue prop changes causing readValueFromStorage to change.
// This ensures the hook reflects the correct localStorage item if its key prop dynamically changes.
useEffect(() => {
setStoredValue(readValueFromStorage());
}, [key, readValueFromStorage]); // Re-run if key or the read function changes.
}, [key, readValueFromStorage]);
return [storedValue, setValue, removeValue];
}

View File

@@ -1,6 +1,7 @@
import { useCallback } from 'react';
import { useLocation, useNavigate } from 'react-router-dom-v5-compat';
import { cloneDeep, isEqual } from 'lodash-es';
import { withBasePath } from 'utils/basePath';
interface NavigateOptions {
replace?: boolean;
@@ -25,12 +26,9 @@ const areUrlsEffectivelySame = (url1: URL, url2: URL): boolean => {
const params1 = new URLSearchParams(url1.search);
const params2 = new URLSearchParams(url2.search);
const allParams = new Set([
...Array.from(params1.keys()),
...Array.from(params2.keys()),
]);
const allParams = new Set([...params1.keys(), ...params2.keys()]);
return Array.from(allParams).every((param) => {
return [...allParams].every((param) => {
if (param === 'compositeQuery') {
try {
const query1 = params1.get('compositeQuery');
@@ -83,11 +81,11 @@ const isDefaultNavigation = (currentUrl: URL, targetUrl: URL): boolean => {
}
// Case 2: Check for new params that didn't exist before
const currentKeys = new Set(Array.from(currentParams.keys()));
const targetKeys = new Set(Array.from(targetParams.keys()));
const currentKeys = new Set(currentParams.keys());
const targetKeys = new Set(targetParams.keys());
// Find keys that exist in target but not in current
const newKeys = Array.from(targetKeys).filter((key) => !currentKeys.has(key));
const newKeys = [...targetKeys].filter((key) => !currentKeys.has(key));
return newKeys.length > 0;
};
@@ -107,19 +105,18 @@ export const useSafeNavigate = (
const safeNavigate = useCallback(
// eslint-disable-next-line sonarjs/cognitive-complexity
(to: string | SafeNavigateParams, options?: NavigateOptions) => {
const currentUrl = new URL(
`${location.pathname}${location.search}`,
window.location.origin,
);
// oxlint-disable-next-line signoz/no-raw-absolute-path
const base = window.location.origin;
const currentUrl = new URL(`${location.pathname}${location.search}`, base);
let targetUrl: URL;
if (typeof to === 'string') {
targetUrl = new URL(to, window.location.origin);
targetUrl = new URL(to, base);
} else {
targetUrl = new URL(
`${to.pathname || location.pathname}${to.search || ''}`,
window.location.origin,
base,
);
}
@@ -130,7 +127,7 @@ export const useSafeNavigate = (
typeof to === 'string'
? to
: `${to.pathname || location.pathname}${to.search || ''}`;
window.open(targetPath, '_blank');
window.open(withBasePath(targetPath), '_blank');
return;
}

View File

@@ -1,3 +1,4 @@
import { createBrowserHistory } from 'history';
import { getBasePath } from 'utils/basePath';
export default createBrowserHistory();
export default createBrowserHistory({ basename: getBasePath() });

View File

@@ -4,6 +4,7 @@ import ROUTES from 'constants/routes';
import { handleContactSupport } from 'container/Integrations/utils';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { Home, LifeBuoy } from 'lucide-react';
import { withBasePath } from 'utils/basePath';
import cloudUrl from '@/assets/Images/cloud.svg';
@@ -11,8 +12,8 @@ import './ErrorBoundaryFallback.styles.scss';
function ErrorBoundaryFallback(): JSX.Element {
const handleReload = (): void => {
// Go to home page
window.location.href = ROUTES.HOME;
// Hard reload resets Sentry.ErrorBoundary state; withBasePath preserves any /signoz/ prefix.
window.location.href = withBasePath(ROUTES.HOME);
};
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();

View File

@@ -98,7 +98,7 @@ function LogsExplorer(): JSX.Element {
setIsLoadingQueries(false);
}, [queryClient]);
const [warning, setWarning] = useState<Warning | undefined>();
const [warning, setWarning] = useState<Warning | undefined>(undefined);
const handleChangeSelectedView = useCallback(
(view: ExplorerViews, querySearchParameters?: ICurrentQueryData): void => {

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