mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-12 20:00:28 +01:00
Compare commits
43 Commits
ns/flamegr
...
dashboard-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49dfea9e2a | ||
|
|
f1465aaec7 | ||
|
|
86ab0fc5de | ||
|
|
eb5a506b5b | ||
|
|
be6293bbd2 | ||
|
|
7d6e645845 | ||
|
|
afce612caf | ||
|
|
1ed852da93 | ||
|
|
23931f25b6 | ||
|
|
e1ad451003 | ||
|
|
9155c635f8 | ||
|
|
870a4b3dc3 | ||
|
|
7eb0095133 | ||
|
|
df26eb1c1d | ||
|
|
e9e7646403 | ||
|
|
36334309bb | ||
|
|
cfcd58b341 | ||
|
|
736a2a751e | ||
|
|
d01e8aff22 | ||
|
|
6ae1033752 | ||
|
|
0587fe41e4 | ||
|
|
75f5a8b82c | ||
|
|
2f541df60e | ||
|
|
45fedefbab | ||
|
|
01ae688b58 | ||
|
|
f4e1465c13 | ||
|
|
b22eef6a65 | ||
|
|
4d3d1ef423 | ||
|
|
c775d7e398 | ||
|
|
27603e09d0 | ||
|
|
7b2882abde | ||
|
|
30f1c2d92d | ||
|
|
446dd4589f | ||
|
|
c0c9039428 | ||
|
|
a014e9c0cb | ||
|
|
b898269ddc | ||
|
|
8fdad21a2e | ||
|
|
2e0d25479a | ||
|
|
73c2c15200 | ||
|
|
34203c781f | ||
|
|
1ba9b90855 | ||
|
|
927951b67a | ||
|
|
05ad8d113d |
4
.github/workflows/build-staging.yaml
vendored
4
.github/workflows/build-staging.yaml
vendored
@@ -64,6 +64,10 @@ jobs:
|
||||
run: |
|
||||
mkdir -p frontend
|
||||
echo 'CI=1' > frontend/.env
|
||||
echo 'VITE_SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
|
||||
echo 'VITE_TUNNEL_URL="${{ secrets.NP_TUNNEL_URL }}"' >> frontend/.env
|
||||
echo 'VITE_TUNNEL_DOMAIN="${{ secrets.NP_TUNNEL_DOMAIN }}"' >> frontend/.env
|
||||
echo 'VITE_PYLON_APP_ID="${{ secrets.NP_PYLON_APP_ID }}"' >> frontend/.env
|
||||
|
||||
@@ -190,7 +190,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.127.1
|
||||
image: signoz/signoz:v0.128.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
# - "6060:6060" # pprof port
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.127.1
|
||||
image: signoz/signoz:v0.128.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
volumes:
|
||||
|
||||
@@ -181,7 +181,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.127.1}
|
||||
image: signoz/signoz:${VERSION:-v0.128.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
|
||||
@@ -109,7 +109,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.127.1}
|
||||
image: signoz/signoz:${VERSION:-v0.128.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
|
||||
@@ -1360,6 +1360,8 @@ components:
|
||||
- sqs
|
||||
- storageaccountsblob
|
||||
- cdnprofile
|
||||
- virtualmachine
|
||||
- appservice
|
||||
- containerapp
|
||||
- aks
|
||||
type: string
|
||||
@@ -2434,13 +2436,6 @@ components:
|
||||
url:
|
||||
type: string
|
||||
type: object
|
||||
DashboardPanelDisplay:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
DashboardTextVariableSpec:
|
||||
properties:
|
||||
constant:
|
||||
@@ -2494,10 +2489,17 @@ components:
|
||||
$ref: '#/components/schemas/DashboardtypesTimePreference'
|
||||
type: object
|
||||
DashboardtypesBuilderQuerySpec:
|
||||
discriminator:
|
||||
mapping:
|
||||
logs: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregation'
|
||||
metrics: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregation'
|
||||
traces: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregation'
|
||||
propertyName: signal
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregation'
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregation'
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregation'
|
||||
type: object
|
||||
DashboardtypesComparisonOperator:
|
||||
enum:
|
||||
- above
|
||||
@@ -2561,13 +2563,12 @@ components:
|
||||
$ref: '#/components/schemas/DashboardtypesDatasourceSpec'
|
||||
type: object
|
||||
display:
|
||||
$ref: '#/components/schemas/CommonDisplay'
|
||||
$ref: '#/components/schemas/DashboardtypesDisplay'
|
||||
duration:
|
||||
type: string
|
||||
layouts:
|
||||
items:
|
||||
$ref: '#/components/schemas/DashboardtypesLayout'
|
||||
nullable: true
|
||||
type: array
|
||||
links:
|
||||
items:
|
||||
@@ -2576,7 +2577,6 @@ components:
|
||||
panels:
|
||||
additionalProperties:
|
||||
$ref: '#/components/schemas/DashboardtypesPanel'
|
||||
nullable: true
|
||||
type: object
|
||||
refreshInterval:
|
||||
type: string
|
||||
@@ -2584,10 +2584,20 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/DashboardtypesVariable'
|
||||
type: array
|
||||
required:
|
||||
- display
|
||||
- variables
|
||||
- panels
|
||||
- layouts
|
||||
type: object
|
||||
DashboardtypesDatasourcePlugin:
|
||||
discriminator:
|
||||
mapping:
|
||||
signoz/Datasource: '#/components/schemas/DashboardtypesDatasourcePluginVariantStruct'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesDatasourcePluginVariantStruct'
|
||||
type: object
|
||||
DashboardtypesDatasourcePluginKind:
|
||||
enum:
|
||||
- signoz/Datasource
|
||||
@@ -2614,6 +2624,15 @@ components:
|
||||
plugin:
|
||||
$ref: '#/components/schemas/DashboardtypesDatasourcePlugin'
|
||||
type: object
|
||||
DashboardtypesDisplay:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
DashboardtypesDynamicVariableSpec:
|
||||
properties:
|
||||
name:
|
||||
@@ -2654,7 +2673,7 @@ components:
|
||||
$ref: '#/components/schemas/DashboardtypesDashboardSpec'
|
||||
tags:
|
||||
items:
|
||||
$ref: '#/components/schemas/TagtypesPostableTag'
|
||||
$ref: '#/components/schemas/TagtypesGettableTag'
|
||||
nullable: true
|
||||
type: array
|
||||
updatedAt:
|
||||
@@ -2731,8 +2750,13 @@ components:
|
||||
- path
|
||||
type: object
|
||||
DashboardtypesLayout:
|
||||
discriminator:
|
||||
mapping:
|
||||
Grid: '#/components/schemas/DashboardtypesLayoutEnvelopeGithubComPersesSpecGoDashboardGridLayoutSpec'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesLayoutEnvelopeGithubComPersesSpecGoDashboardGridLayoutSpec'
|
||||
type: object
|
||||
DashboardtypesLayoutEnvelopeGithubComPersesSpecGoDashboardGridLayoutSpec:
|
||||
properties:
|
||||
kind:
|
||||
@@ -2772,6 +2796,11 @@ components:
|
||||
- solid
|
||||
- dashed
|
||||
type: string
|
||||
DashboardtypesListOrder:
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
type: string
|
||||
DashboardtypesListPanelSpec:
|
||||
properties:
|
||||
selectFields:
|
||||
@@ -2779,6 +2808,12 @@ components:
|
||||
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
|
||||
type: array
|
||||
type: object
|
||||
DashboardtypesListSort:
|
||||
enum:
|
||||
- updated_at
|
||||
- created_at
|
||||
- name
|
||||
type: string
|
||||
DashboardtypesListVariableSpec:
|
||||
properties:
|
||||
allowAllValue:
|
||||
@@ -2792,7 +2827,7 @@ components:
|
||||
defaultValue:
|
||||
$ref: '#/components/schemas/VariableDefaultValue'
|
||||
display:
|
||||
$ref: '#/components/schemas/VariableDisplay'
|
||||
$ref: '#/components/schemas/DashboardtypesDisplay'
|
||||
name:
|
||||
type: string
|
||||
plugin:
|
||||
@@ -2800,6 +2835,136 @@ components:
|
||||
sort:
|
||||
nullable: true
|
||||
type: string
|
||||
required:
|
||||
- display
|
||||
type: object
|
||||
DashboardtypesListableDashboardForUserV2:
|
||||
properties:
|
||||
dashboards:
|
||||
items:
|
||||
$ref: '#/components/schemas/DashboardtypesListedDashboardForUserV2'
|
||||
type: array
|
||||
tags:
|
||||
items:
|
||||
$ref: '#/components/schemas/TagtypesGettableTag'
|
||||
type: array
|
||||
total:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- dashboards
|
||||
- total
|
||||
- tags
|
||||
type: object
|
||||
DashboardtypesListableDashboardV2:
|
||||
properties:
|
||||
dashboards:
|
||||
items:
|
||||
$ref: '#/components/schemas/DashboardtypesListedDashboardV2'
|
||||
type: array
|
||||
tags:
|
||||
items:
|
||||
$ref: '#/components/schemas/TagtypesGettableTag'
|
||||
type: array
|
||||
total:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- dashboards
|
||||
- total
|
||||
- tags
|
||||
type: object
|
||||
DashboardtypesListedDashboardForUserV2:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
locked:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
pinned:
|
||||
type: boolean
|
||||
schemaVersion:
|
||||
type: string
|
||||
source:
|
||||
$ref: '#/components/schemas/DashboardtypesSource'
|
||||
spec:
|
||||
$ref: '#/components/schemas/DashboardtypesListedDashboardV2Spec'
|
||||
tags:
|
||||
items:
|
||||
$ref: '#/components/schemas/TagtypesGettableTag'
|
||||
type: array
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- orgId
|
||||
- locked
|
||||
- source
|
||||
- schemaVersion
|
||||
- name
|
||||
- tags
|
||||
- spec
|
||||
- pinned
|
||||
type: object
|
||||
DashboardtypesListedDashboardV2:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
locked:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
schemaVersion:
|
||||
type: string
|
||||
source:
|
||||
$ref: '#/components/schemas/DashboardtypesSource'
|
||||
spec:
|
||||
$ref: '#/components/schemas/DashboardtypesListedDashboardV2Spec'
|
||||
tags:
|
||||
items:
|
||||
$ref: '#/components/schemas/TagtypesGettableTag'
|
||||
type: array
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- orgId
|
||||
- locked
|
||||
- source
|
||||
- schemaVersion
|
||||
- name
|
||||
- tags
|
||||
- spec
|
||||
type: object
|
||||
DashboardtypesListedDashboardV2Spec:
|
||||
properties:
|
||||
display:
|
||||
$ref: '#/components/schemas/DashboardtypesDisplay'
|
||||
type: object
|
||||
DashboardtypesNumberPanelSpec:
|
||||
properties:
|
||||
@@ -2819,6 +2984,9 @@ components:
|
||||
$ref: '#/components/schemas/DashboardtypesPanelKind'
|
||||
spec:
|
||||
$ref: '#/components/schemas/DashboardtypesPanelSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
DashboardtypesPanelFormatting:
|
||||
properties:
|
||||
@@ -2832,6 +3000,16 @@ components:
|
||||
- Panel
|
||||
type: string
|
||||
DashboardtypesPanelPlugin:
|
||||
discriminator:
|
||||
mapping:
|
||||
signoz/BarChartPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBarChartPanelSpec'
|
||||
signoz/HistogramPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHistogramPanelSpec'
|
||||
signoz/ListPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesListPanelSpec'
|
||||
signoz/NumberPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesNumberPanelSpec'
|
||||
signoz/PieChartPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesPieChartPanelSpec'
|
||||
signoz/TablePanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTablePanelSpec'
|
||||
signoz/TimeSeriesPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTimeSeriesPanelSpec'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTimeSeriesPanelSpec'
|
||||
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBarChartPanelSpec'
|
||||
@@ -2840,6 +3018,7 @@ components:
|
||||
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTablePanelSpec'
|
||||
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHistogramPanelSpec'
|
||||
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesListPanelSpec'
|
||||
type: object
|
||||
DashboardtypesPanelPluginKind:
|
||||
enum:
|
||||
- signoz/TimeSeriesPanel
|
||||
@@ -2937,7 +3116,7 @@ components:
|
||||
DashboardtypesPanelSpec:
|
||||
properties:
|
||||
display:
|
||||
$ref: '#/components/schemas/DashboardPanelDisplay'
|
||||
$ref: '#/components/schemas/DashboardtypesDisplay'
|
||||
links:
|
||||
items:
|
||||
$ref: '#/components/schemas/DashboardLink'
|
||||
@@ -2947,7 +3126,12 @@ components:
|
||||
queries:
|
||||
items:
|
||||
$ref: '#/components/schemas/DashboardtypesQuery'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- display
|
||||
- plugin
|
||||
- queries
|
||||
type: object
|
||||
DashboardtypesPatchOp:
|
||||
enum:
|
||||
@@ -3016,8 +3200,20 @@ components:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5RequestType'
|
||||
spec:
|
||||
$ref: '#/components/schemas/DashboardtypesQuerySpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
DashboardtypesQueryPlugin:
|
||||
discriminator:
|
||||
mapping:
|
||||
signoz/BuilderQuery: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBuilderQuerySpec'
|
||||
signoz/ClickHouseSQL: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQuery'
|
||||
signoz/CompositeQuery: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5CompositeQuery'
|
||||
signoz/Formula: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderFormula'
|
||||
signoz/PromQLQuery: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5PromQuery'
|
||||
signoz/TraceOperator: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderTraceOperator'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBuilderQuerySpec'
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5CompositeQuery'
|
||||
@@ -3025,6 +3221,7 @@ components:
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5PromQuery'
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQuery'
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderTraceOperator'
|
||||
type: object
|
||||
DashboardtypesQueryPluginKind:
|
||||
enum:
|
||||
- signoz/BuilderQuery
|
||||
@@ -3112,6 +3309,8 @@ components:
|
||||
type: string
|
||||
plugin:
|
||||
$ref: '#/components/schemas/DashboardtypesQueryPlugin'
|
||||
required:
|
||||
- plugin
|
||||
type: object
|
||||
DashboardtypesQueryVariableSpec:
|
||||
properties:
|
||||
@@ -3279,9 +3478,15 @@ components:
|
||||
type: boolean
|
||||
type: object
|
||||
DashboardtypesVariable:
|
||||
discriminator:
|
||||
mapping:
|
||||
ListVariable: '#/components/schemas/DashboardtypesVariableEnvelopeGithubComSigNozSignozPkgTypesDashboardtypesListVariableSpec'
|
||||
TextVariable: '#/components/schemas/DashboardtypesVariableEnvelopeGithubComPersesSpecGoDashboardTextVariableSpec'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesVariableEnvelopeGithubComSigNozSignozPkgTypesDashboardtypesListVariableSpec'
|
||||
- $ref: '#/components/schemas/DashboardtypesVariableEnvelopeGithubComPersesSpecGoDashboardTextVariableSpec'
|
||||
type: object
|
||||
DashboardtypesVariableEnvelopeGithubComPersesSpecGoDashboardTextVariableSpec:
|
||||
properties:
|
||||
kind:
|
||||
@@ -3307,10 +3512,17 @@ components:
|
||||
- spec
|
||||
type: object
|
||||
DashboardtypesVariablePlugin:
|
||||
discriminator:
|
||||
mapping:
|
||||
signoz/CustomVariable: '#/components/schemas/DashboardtypesVariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesCustomVariableSpec'
|
||||
signoz/DynamicVariable: '#/components/schemas/DashboardtypesVariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDynamicVariableSpec'
|
||||
signoz/QueryVariable: '#/components/schemas/DashboardtypesVariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesQueryVariableSpec'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesVariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDynamicVariableSpec'
|
||||
- $ref: '#/components/schemas/DashboardtypesVariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesQueryVariableSpec'
|
||||
- $ref: '#/components/schemas/DashboardtypesVariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesCustomVariableSpec'
|
||||
type: object
|
||||
DashboardtypesVariablePluginKind:
|
||||
enum:
|
||||
- signoz/DynamicVariable
|
||||
@@ -5513,11 +5725,15 @@ components:
|
||||
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
|
||||
type: array
|
||||
signal:
|
||||
$ref: '#/components/schemas/TelemetrytypesSignal'
|
||||
enum:
|
||||
- logs
|
||||
type: string
|
||||
source:
|
||||
$ref: '#/components/schemas/TelemetrytypesSource'
|
||||
stepInterval:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Step'
|
||||
required:
|
||||
- signal
|
||||
type: object
|
||||
Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregation:
|
||||
properties:
|
||||
@@ -5564,11 +5780,15 @@ components:
|
||||
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
|
||||
type: array
|
||||
signal:
|
||||
$ref: '#/components/schemas/TelemetrytypesSignal'
|
||||
enum:
|
||||
- metrics
|
||||
type: string
|
||||
source:
|
||||
$ref: '#/components/schemas/TelemetrytypesSource'
|
||||
stepInterval:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Step'
|
||||
required:
|
||||
- signal
|
||||
type: object
|
||||
Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregation:
|
||||
properties:
|
||||
@@ -5615,11 +5835,15 @@ components:
|
||||
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
|
||||
type: array
|
||||
signal:
|
||||
$ref: '#/components/schemas/TelemetrytypesSignal'
|
||||
enum:
|
||||
- traces
|
||||
type: string
|
||||
source:
|
||||
$ref: '#/components/schemas/TelemetrytypesSource'
|
||||
stepInterval:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Step'
|
||||
required:
|
||||
- signal
|
||||
type: object
|
||||
Querybuildertypesv5QueryBuilderTraceOperator:
|
||||
properties:
|
||||
@@ -6724,11 +6948,6 @@ components:
|
||||
type: object
|
||||
SpantypesGettableWaterfallTrace:
|
||||
properties:
|
||||
aggregations:
|
||||
items:
|
||||
$ref: '#/components/schemas/SpantypesSpanAggregationResult'
|
||||
nullable: true
|
||||
type: array
|
||||
endTimestampMillis:
|
||||
minimum: 0
|
||||
type: integer
|
||||
@@ -6816,14 +7035,6 @@ components:
|
||||
type: object
|
||||
SpantypesPostableWaterfall:
|
||||
properties:
|
||||
aggregations:
|
||||
items:
|
||||
$ref: '#/components/schemas/SpantypesSpanAggregation'
|
||||
nullable: true
|
||||
type: array
|
||||
limit:
|
||||
minimum: 0
|
||||
type: integer
|
||||
selectedSpanId:
|
||||
type: string
|
||||
uncollapsedSpans:
|
||||
@@ -7073,6 +7284,16 @@ components:
|
||||
required:
|
||||
- references
|
||||
type: object
|
||||
TagtypesGettableTag:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- value
|
||||
type: object
|
||||
TagtypesPostableTag:
|
||||
properties:
|
||||
key:
|
||||
@@ -13108,6 +13329,82 @@ paths:
|
||||
tags:
|
||||
- preferences
|
||||
/api/v2/dashboards:
|
||||
get:
|
||||
deprecated: false
|
||||
description: Returns a page of v2-shape dashboards for the org. This is the
|
||||
pure, user-independent list — it carries no pin state. Use ListDashboardsForUserV2
|
||||
for the personalized, pin-aware list. Supports a filter DSL (`query`), sort
|
||||
(`updated_at`/`created_at`/`name`), order (`asc`/`desc`), and offset-based
|
||||
pagination (`limit`/`offset`).
|
||||
operationId: ListDashboardsV2
|
||||
parameters:
|
||||
- in: query
|
||||
name: query
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: sort
|
||||
schema:
|
||||
$ref: '#/components/schemas/DashboardtypesListSort'
|
||||
- in: query
|
||||
name: order
|
||||
schema:
|
||||
$ref: '#/components/schemas/DashboardtypesListOrder'
|
||||
- in: query
|
||||
name: limit
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: offset
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/DashboardtypesListableDashboardV2'
|
||||
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 dashboards (v2)
|
||||
tags:
|
||||
- dashboard
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint creates a dashboard in the v2 format that follows
|
||||
@@ -13166,6 +13463,62 @@ paths:
|
||||
tags:
|
||||
- dashboard
|
||||
/api/v2/dashboards/{id}:
|
||||
delete:
|
||||
deprecated: false
|
||||
description: This endpoint deletes a v2-shape dashboard along with its tag relations.
|
||||
Locked dashboards are rejected.
|
||||
operationId: DeleteDashboardV2
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
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:
|
||||
- EDITOR
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
summary: Delete dashboard (v2)
|
||||
tags:
|
||||
- dashboard
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint returns a v2-shape dashboard.
|
||||
@@ -20388,6 +20741,196 @@ paths:
|
||||
summary: Update my user v2
|
||||
tags:
|
||||
- users
|
||||
/api/v2/users/me/dashboards:
|
||||
get:
|
||||
deprecated: false
|
||||
description: 'Same as ListDashboardsV2 but personalized for the calling user:
|
||||
each dashboard carries the caller''s `pinned` state, and pinned dashboards
|
||||
float to the top of the requested ordering. Supports the same filter DSL,
|
||||
sort, order, and pagination.'
|
||||
operationId: ListDashboardsForUserV2
|
||||
parameters:
|
||||
- in: query
|
||||
name: query
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: sort
|
||||
schema:
|
||||
$ref: '#/components/schemas/DashboardtypesListSort'
|
||||
- in: query
|
||||
name: order
|
||||
schema:
|
||||
$ref: '#/components/schemas/DashboardtypesListOrder'
|
||||
- in: query
|
||||
name: limit
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: offset
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/DashboardtypesListableDashboardForUserV2'
|
||||
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 dashboards for the current user (v2)
|
||||
tags:
|
||||
- dashboard
|
||||
/api/v2/users/me/dashboards/{id}/pins:
|
||||
delete:
|
||||
deprecated: false
|
||||
description: Removes the pin for the calling user. Idempotent — unpinning a
|
||||
dashboard that wasn't pinned still returns 204.
|
||||
operationId: UnpinDashboardV2
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
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
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Unpin a dashboard for the current user (v2)
|
||||
tags:
|
||||
- dashboard
|
||||
put:
|
||||
deprecated: false
|
||||
description: Pins the dashboard for the calling user. A user can pin at most
|
||||
10 dashboards; pinning when at the limit returns 409. Re-pinning an already-pinned
|
||||
dashboard is a no-op success.
|
||||
operationId: PinDashboardV2
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
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
|
||||
"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:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Pin a dashboard for the current user (v2)
|
||||
tags:
|
||||
- dashboard
|
||||
/api/v2/users/me/factor_password:
|
||||
put:
|
||||
deprecated: false
|
||||
@@ -20679,76 +21222,6 @@ paths:
|
||||
summary: Get flamegraph view for a trace
|
||||
tags:
|
||||
- tracedetail
|
||||
/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/SpantypesPostableWaterfall'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/SpantypesGettableWaterfallTrace'
|
||||
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/v4/traces/{traceID}/waterfall:
|
||||
post:
|
||||
deprecated: false
|
||||
|
||||
@@ -333,6 +333,50 @@ func (Step) JSONSchema() (jsonschema.Schema, error) {
|
||||
}
|
||||
```
|
||||
|
||||
### `oneOf` with a discriminator
|
||||
|
||||
For a sum type whose variants are keyed by a property (e.g. `kind`), expose the variants via `JSONSchemaOneOf()` and add a discriminator. Without it, code generators intersect the variants (`A & B & C`) instead of producing a clean discriminated union (`A | B | C`).
|
||||
|
||||
The parent keeps its `JSONSchemaOneOf()` (the `oneOf` itself) and *additionally* tags it via `PrepareJSONSchema` with the `x-signoz-discriminator` extension; `signoz.attachDiscriminators` then promotes that marker to a real OpenAPI 3 `discriminator` (and strips the duplicate parent properties) after reflection.
|
||||
|
||||
```go
|
||||
// On the parent: expose the oneOf variants...
|
||||
func (Plugin) JSONSchemaOneOf() []any {
|
||||
return []any{FooVariant{}}
|
||||
}
|
||||
|
||||
// ...and tag that same oneOf with the discriminator marker.
|
||||
func (Plugin) PrepareJSONSchema(s *jsonschema.Schema) error {
|
||||
if s.ExtraProperties == nil {
|
||||
s.ExtraProperties = map[string]any{}
|
||||
}
|
||||
s.ExtraProperties["x-signoz-discriminator"] = map[string]any{
|
||||
"propertyName": "kind",
|
||||
"mapping": map[string]string{
|
||||
"signoz/Foo": "#/components/schemas/FooVariant",
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Each variant must declare the discriminator property (`kind`) and mark it `required`.
|
||||
|
||||
This produces the following in the generated OpenAPI spec:
|
||||
|
||||
```yaml
|
||||
Plugin:
|
||||
discriminator:
|
||||
propertyName: kind
|
||||
mapping:
|
||||
signoz/Foo: '#/components/schemas/FooVariant'
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/FooVariant'
|
||||
type: object
|
||||
```
|
||||
|
||||
Note the discriminator property lives in the variants, not on the parent — the parent is only the union.
|
||||
|
||||
|
||||
## What should I remember?
|
||||
|
||||
|
||||
@@ -229,10 +229,39 @@ func (module *module) PatchV2(ctx context.Context, orgID valuer.UUID, id valuer.
|
||||
return module.pkgDashboardModule.PatchV2(ctx, orgID, id, updatedBy, patch)
|
||||
}
|
||||
|
||||
func (module *module) DeleteV2(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
return module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.store.DeletePublic(ctx, id.String()); err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
return module.pkgDashboardModule.DeleteV2(ctx, orgID, id)
|
||||
})
|
||||
}
|
||||
|
||||
func (module *module) LockUnlockV2(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, isAdmin bool, lock bool) error {
|
||||
return module.pkgDashboardModule.LockUnlockV2(ctx, orgID, id, updatedBy, isAdmin, lock)
|
||||
}
|
||||
|
||||
func (module *module) ListV2(ctx context.Context, orgID valuer.UUID, params *dashboardtypes.ListDashboardsV2Params) (*dashboardtypes.ListableDashboardV2, error) {
|
||||
return module.pkgDashboardModule.ListV2(ctx, orgID, params)
|
||||
}
|
||||
|
||||
func (module *module) ListForUserV2(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, params *dashboardtypes.ListDashboardsV2Params) (*dashboardtypes.ListableDashboardForUserV2, error) {
|
||||
return module.pkgDashboardModule.ListForUserV2(ctx, orgID, userID, params)
|
||||
}
|
||||
|
||||
func (module *module) PinV2(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, id valuer.UUID) error {
|
||||
return module.pkgDashboardModule.PinV2(ctx, orgID, userID, id)
|
||||
}
|
||||
|
||||
func (module *module) UnpinV2(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, id valuer.UUID) error {
|
||||
return module.pkgDashboardModule.UnpinV2(ctx, orgID, userID, id)
|
||||
}
|
||||
|
||||
func (module *module) DeletePreferencesForUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
|
||||
return module.pkgDashboardModule.DeletePreferencesForUser(ctx, orgID, userID)
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.Get(ctx, orgID, id)
|
||||
}
|
||||
|
||||
@@ -185,6 +185,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
s.config.APIServer.Timeout.Default,
|
||||
s.config.APIServer.Timeout.Max,
|
||||
).Wrap)
|
||||
r.Use(middleware.NewResource(s.signoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewAudit(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes, s.signoz.Auditor).Wrap)
|
||||
r.Use(middleware.NewComment().Wrap)
|
||||
|
||||
|
||||
@@ -16,10 +16,11 @@ func newFormatter(dialect schema.Dialect) sqlstore.SQLFormatter {
|
||||
}
|
||||
|
||||
func (f *formatter) JSONExtractString(column, path string) []byte {
|
||||
var sql []byte
|
||||
sql = f.bunf.AppendIdent(sql, column)
|
||||
sql = append(sql, f.convertJSONPathToPostgres(path)...)
|
||||
return sql
|
||||
ops := f.convertJSONPathToPostgres(path)
|
||||
if len(ops) == 0 {
|
||||
return f.bunf.AppendIdent(nil, column)
|
||||
}
|
||||
return append(f.TextToJsonColumn(column), ops...)
|
||||
}
|
||||
|
||||
func (f *formatter) JSONType(column, path string) []byte {
|
||||
|
||||
@@ -18,19 +18,19 @@ func TestJSONExtractString(t *testing.T) {
|
||||
name: "simple path",
|
||||
column: "data",
|
||||
path: "$.field",
|
||||
expected: `"data"->>'field'`,
|
||||
expected: `"data"::jsonb->>'field'`,
|
||||
},
|
||||
{
|
||||
name: "nested path",
|
||||
column: "metadata",
|
||||
path: "$.user.name",
|
||||
expected: `"metadata"->'user'->>'name'`,
|
||||
expected: `"metadata"::jsonb->'user'->>'name'`,
|
||||
},
|
||||
{
|
||||
name: "deeply nested path",
|
||||
column: "json_col",
|
||||
path: "$.level1.level2.level3",
|
||||
expected: `"json_col"->'level1'->'level2'->>'level3'`,
|
||||
expected: `"json_col"::jsonb->'level1'->'level2'->>'level3'`,
|
||||
},
|
||||
{
|
||||
name: "root path",
|
||||
|
||||
@@ -5,6 +5,13 @@ import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/getTriggered';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetAlerts` hook (or `getAlerts` fetcher) from
|
||||
* `api/generated/services/alerts` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const getTriggered = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createEmail';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createMsTeams';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createOpsgenie';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createPager';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createSlack';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createWebhook';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/delete';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useDeleteChannelByID` hook (or `deleteChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const deleteChannel = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editEmail';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editEmail = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editMsTeams';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editMsTeams = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editOpsgenie';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editOpsgenie = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps> | ErrorResponse> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editPager';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editPager = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editSlack';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editSlack = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editWebhook';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editWebhook = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -5,6 +5,13 @@ import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/get';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetChannelByID` hook (or `getChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const get = async (props: Props): Promise<SuccessResponseV2<Channels>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/channels/${props.id}`);
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { Channels, PayloadProps } from 'types/api/channels/getAll';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useListChannels` hook (or `listChannels` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const getAll = async (): Promise<SuccessResponseV2<Channels[]>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>('/channels');
|
||||
|
||||
@@ -5,6 +5,13 @@ import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constan
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { CreatePublicDashboardProps } from 'types/api/dashboard/public/create';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreatePublicDashboard` hook (or `createPublicDashboard` fetcher) from
|
||||
* `api/generated/services/dashboard` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const createPublicDashboard = async (
|
||||
props: CreatePublicDashboardProps,
|
||||
): Promise<SuccessResponseV2<CreatePublicDashboardProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { GetPublicDashboardDataProps, PayloadProps,PublicDashboardDataProps } from 'types/api/dashboard/public/get';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetPublicDashboardData` hook (or `getPublicDashboardData` fetcher) from
|
||||
* `api/generated/services/dashboard` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const getPublicDashboardData = async (props: GetPublicDashboardDataProps): Promise<SuccessResponseV2<PublicDashboardDataProps>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/public/dashboards/${props.id}`);
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { GetPublicDashboardMetaProps, PayloadProps,PublicDashboardMetaProps } from 'types/api/dashboard/public/getMeta';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetPublicDashboard` hook (or `getPublicDashboard` fetcher) from
|
||||
* `api/generated/services/dashboard` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const getPublicDashboardMeta = async (props: GetPublicDashboardMetaProps): Promise<SuccessResponseV2<PublicDashboardMetaProps>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/dashboards/${props.id}/public`);
|
||||
|
||||
@@ -6,6 +6,13 @@ import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { GetPublicDashboardWidgetDataProps } from 'types/api/dashboard/public/getWidgetData';
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetPublicDashboardWidgetQueryRange` hook (or `getPublicDashboardWidgetQueryRange` fetcher) from
|
||||
* `api/generated/services/dashboard` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const getPublicDashboardWidgetData = async (props: GetPublicDashboardWidgetDataProps): Promise<SuccessResponseV2<MetricRangePayloadV5>> => {
|
||||
try {
|
||||
const response = await axios.get(`/public/dashboards/${props.id}/widgets/${props.index}/query_range`, {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps,RevokePublicDashboardAccessProps } from 'types/api/dashboard/public/delete';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useDeletePublicDashboard` hook (or `deletePublicDashboard` fetcher) from
|
||||
* `api/generated/services/dashboard` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const revokePublicDashboardAccess = async (
|
||||
props: RevokePublicDashboardAccessProps,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -5,6 +5,13 @@ import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constan
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { UpdatePublicDashboardProps } from 'types/api/dashboard/public/update';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdatePublicDashboard` hook (or `updatePublicDashboard` fetcher) from
|
||||
* `api/generated/services/dashboard` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const updatePublicDashboard = async (
|
||||
props: UpdatePublicDashboardProps,
|
||||
): Promise<SuccessResponseV2<UpdatePublicDashboardProps>> => {
|
||||
|
||||
@@ -9,6 +9,13 @@ interface ISubstituteVars {
|
||||
compositeQuery: ICompositeMetricQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useReplaceVariables` hook (or `replaceVariables` fetcher) from
|
||||
* `api/generated/services/querier` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
export const getSubstituteVars = async (
|
||||
props?: Partial<QueryRangePayloadV5>,
|
||||
signal?: AbortSignal,
|
||||
|
||||
@@ -8,6 +8,12 @@ import { FieldKeyResponse } from 'types/api/dynamicVariables/getFieldKeys';
|
||||
* Get field keys for a given signal type
|
||||
* @param signal Type of signal (traces, logs, metrics)
|
||||
* @param name Optional search text
|
||||
*
|
||||
* @deprecated Use the generated `useGetFieldsKeys` hook (or `getFieldsKeys` fetcher) from
|
||||
* `api/generated/services/fields` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
export const getFieldKeys = async (
|
||||
signal?: 'traces' | 'logs' | 'metrics',
|
||||
|
||||
@@ -11,6 +11,12 @@ import { FieldValueResponse } from 'types/api/dynamicVariables/getFieldValues';
|
||||
* @param name Name of the attribute for which values are being fetched
|
||||
* @param value Optional search text
|
||||
* @param existingQuery Optional existing query - across all present dynamic variables
|
||||
*
|
||||
* @deprecated Use the generated `useGetFieldsValues` hook (or `getFieldsValues` fetcher) from
|
||||
* `api/generated/services/fields` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
export const getFieldValues = async (
|
||||
signal?: 'traces' | 'logs' | 'metrics',
|
||||
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
DashboardtypesPostablePublicDashboardDTO,
|
||||
DashboardtypesUpdatableDashboardV2DTO,
|
||||
DashboardtypesUpdatablePublicDashboardDTO,
|
||||
DeleteDashboardV2PathParameters,
|
||||
DeletePublicDashboardPathParameters,
|
||||
GetDashboardV2200,
|
||||
GetDashboardV2PathParameters,
|
||||
@@ -35,11 +36,17 @@ import type {
|
||||
GetPublicDashboardPathParameters,
|
||||
GetPublicDashboardWidgetQueryRange200,
|
||||
GetPublicDashboardWidgetQueryRangePathParameters,
|
||||
ListDashboardsForUserV2200,
|
||||
ListDashboardsForUserV2Params,
|
||||
ListDashboardsV2200,
|
||||
ListDashboardsV2Params,
|
||||
LockDashboardV2PathParameters,
|
||||
PatchDashboardV2200,
|
||||
PatchDashboardV2PathParameters,
|
||||
PinDashboardV2PathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
UnlockDashboardV2PathParameters,
|
||||
UnpinDashboardV2PathParameters,
|
||||
UpdateDashboardV2200,
|
||||
UpdateDashboardV2PathParameters,
|
||||
UpdatePublicDashboardPathParameters,
|
||||
@@ -641,6 +648,103 @@ export const invalidateGetPublicDashboardWidgetQueryRange = async (
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a page of v2-shape dashboards for the org. This is the pure, user-independent list — it carries no pin state. Use ListDashboardsForUserV2 for the personalized, pin-aware list. Supports a filter DSL (`query`), sort (`updated_at`/`created_at`/`name`), order (`asc`/`desc`), and offset-based pagination (`limit`/`offset`).
|
||||
* @summary List dashboards (v2)
|
||||
*/
|
||||
export const listDashboardsV2 = (
|
||||
params?: ListDashboardsV2Params,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListDashboardsV2200>({
|
||||
url: `/api/v2/dashboards`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListDashboardsV2QueryKey = (
|
||||
params?: ListDashboardsV2Params,
|
||||
) => {
|
||||
return [`/api/v2/dashboards`, ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getListDashboardsV2QueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listDashboardsV2>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: ListDashboardsV2Params,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listDashboardsV2>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListDashboardsV2QueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listDashboardsV2>>> = ({
|
||||
signal,
|
||||
}) => listDashboardsV2(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listDashboardsV2>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListDashboardsV2QueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listDashboardsV2>>
|
||||
>;
|
||||
export type ListDashboardsV2QueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List dashboards (v2)
|
||||
*/
|
||||
|
||||
export function useListDashboardsV2<
|
||||
TData = Awaited<ReturnType<typeof listDashboardsV2>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: ListDashboardsV2Params,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listDashboardsV2>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListDashboardsV2QueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List dashboards (v2)
|
||||
*/
|
||||
export const invalidateListDashboardsV2 = async (
|
||||
queryClient: QueryClient,
|
||||
params?: ListDashboardsV2Params,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListDashboardsV2QueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint creates a dashboard in the v2 format that follows Perses spec.
|
||||
* @summary Create dashboard (v2)
|
||||
@@ -724,6 +828,85 @@ export const useCreateDashboardV2 = <
|
||||
> => {
|
||||
return useMutation(getCreateDashboardV2MutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint deletes a v2-shape dashboard along with its tag relations. Locked dashboards are rejected.
|
||||
* @summary Delete dashboard (v2)
|
||||
*/
|
||||
export const deleteDashboardV2 = (
|
||||
{ id }: DeleteDashboardV2PathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<string>({
|
||||
url: `/api/v2/dashboards/${id}`,
|
||||
method: 'DELETE',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteDashboardV2MutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: DeleteDashboardV2PathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: DeleteDashboardV2PathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteDashboardV2'];
|
||||
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 deleteDashboardV2>>,
|
||||
{ pathParams: DeleteDashboardV2PathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteDashboardV2(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteDashboardV2MutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteDashboardV2>>
|
||||
>;
|
||||
|
||||
export type DeleteDashboardV2MutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete dashboard (v2)
|
||||
*/
|
||||
export const useDeleteDashboardV2 = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: DeleteDashboardV2PathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: DeleteDashboardV2PathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getDeleteDashboardV2MutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint returns a v2-shape dashboard.
|
||||
* @summary Get dashboard (v2)
|
||||
@@ -1181,3 +1364,260 @@ export const useLockDashboardV2 = <
|
||||
> => {
|
||||
return useMutation(getLockDashboardV2MutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* Same as ListDashboardsV2 but personalized for the calling user: each dashboard carries the caller's `pinned` state, and pinned dashboards float to the top of the requested ordering. Supports the same filter DSL, sort, order, and pagination.
|
||||
* @summary List dashboards for the current user (v2)
|
||||
*/
|
||||
export const listDashboardsForUserV2 = (
|
||||
params?: ListDashboardsForUserV2Params,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListDashboardsForUserV2200>({
|
||||
url: `/api/v2/users/me/dashboards`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListDashboardsForUserV2QueryKey = (
|
||||
params?: ListDashboardsForUserV2Params,
|
||||
) => {
|
||||
return [`/api/v2/users/me/dashboards`, ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getListDashboardsForUserV2QueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listDashboardsForUserV2>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: ListDashboardsForUserV2Params,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listDashboardsForUserV2>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListDashboardsForUserV2QueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof listDashboardsForUserV2>>
|
||||
> = ({ signal }) => listDashboardsForUserV2(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listDashboardsForUserV2>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListDashboardsForUserV2QueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listDashboardsForUserV2>>
|
||||
>;
|
||||
export type ListDashboardsForUserV2QueryError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List dashboards for the current user (v2)
|
||||
*/
|
||||
|
||||
export function useListDashboardsForUserV2<
|
||||
TData = Awaited<ReturnType<typeof listDashboardsForUserV2>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: ListDashboardsForUserV2Params,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listDashboardsForUserV2>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListDashboardsForUserV2QueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List dashboards for the current user (v2)
|
||||
*/
|
||||
export const invalidateListDashboardsForUserV2 = async (
|
||||
queryClient: QueryClient,
|
||||
params?: ListDashboardsForUserV2Params,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListDashboardsForUserV2QueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the pin for the calling user. Idempotent — unpinning a dashboard that wasn't pinned still returns 204.
|
||||
* @summary Unpin a dashboard for the current user (v2)
|
||||
*/
|
||||
export const unpinDashboardV2 = (
|
||||
{ id }: UnpinDashboardV2PathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<string>({
|
||||
url: `/api/v2/users/me/dashboards/${id}/pins`,
|
||||
method: 'DELETE',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUnpinDashboardV2MutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof unpinDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: UnpinDashboardV2PathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof unpinDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: UnpinDashboardV2PathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['unpinDashboardV2'];
|
||||
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 unpinDashboardV2>>,
|
||||
{ pathParams: UnpinDashboardV2PathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return unpinDashboardV2(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UnpinDashboardV2MutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof unpinDashboardV2>>
|
||||
>;
|
||||
|
||||
export type UnpinDashboardV2MutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Unpin a dashboard for the current user (v2)
|
||||
*/
|
||||
export const useUnpinDashboardV2 = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof unpinDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: UnpinDashboardV2PathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof unpinDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: UnpinDashboardV2PathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getUnpinDashboardV2MutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* Pins the dashboard for the calling user. A user can pin at most 10 dashboards; pinning when at the limit returns 409. Re-pinning an already-pinned dashboard is a no-op success.
|
||||
* @summary Pin a dashboard for the current user (v2)
|
||||
*/
|
||||
export const pinDashboardV2 = (
|
||||
{ id }: PinDashboardV2PathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<string>({
|
||||
url: `/api/v2/users/me/dashboards/${id}/pins`,
|
||||
method: 'PUT',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getPinDashboardV2MutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof pinDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: PinDashboardV2PathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof pinDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: PinDashboardV2PathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['pinDashboardV2'];
|
||||
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 pinDashboardV2>>,
|
||||
{ pathParams: PinDashboardV2PathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return pinDashboardV2(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type PinDashboardV2MutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof pinDashboardV2>>
|
||||
>;
|
||||
|
||||
export type PinDashboardV2MutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Pin a dashboard for the current user (v2)
|
||||
*/
|
||||
export const usePinDashboardV2 = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof pinDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: PinDashboardV2PathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof pinDashboardV2>>,
|
||||
TError,
|
||||
{ pathParams: PinDashboardV2PathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getPinDashboardV2MutationOptions(options));
|
||||
};
|
||||
|
||||
@@ -2651,6 +2651,8 @@ export enum CloudintegrationtypesServiceIDDTO {
|
||||
sqs = 'sqs',
|
||||
storageaccountsblob = 'storageaccountsblob',
|
||||
cdnprofile = 'cdnprofile',
|
||||
virtualmachine = 'virtualmachine',
|
||||
appservice = 'appservice',
|
||||
containerapp = 'containerapp',
|
||||
aks = 'aks',
|
||||
}
|
||||
@@ -3154,17 +3156,6 @@ export interface DashboardLinkDTO {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface DashboardPanelDisplayDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface VariableDisplayDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -3493,6 +3484,9 @@ export interface TelemetrytypesTelemetryFieldKeyDTO {
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export enum Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregationDTOSignal {
|
||||
logs = 'logs',
|
||||
}
|
||||
export enum TelemetrytypesSourceDTO {
|
||||
meter = 'meter',
|
||||
}
|
||||
@@ -3548,7 +3542,11 @@ export interface Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTyp
|
||||
* @type array
|
||||
*/
|
||||
selectFields?: TelemetrytypesTelemetryFieldKeyDTO[];
|
||||
signal?: TelemetrytypesSignalDTO;
|
||||
/**
|
||||
* @enum logs
|
||||
* @type string
|
||||
*/
|
||||
signal: Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregationDTOSignal;
|
||||
source?: TelemetrytypesSourceDTO;
|
||||
stepInterval?: Querybuildertypesv5StepDTO;
|
||||
}
|
||||
@@ -3614,6 +3612,9 @@ export interface Querybuildertypesv5MetricAggregationDTO {
|
||||
timeAggregation?: MetrictypesTimeAggregationDTO;
|
||||
}
|
||||
|
||||
export enum Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregationDTOSignal {
|
||||
metrics = 'metrics',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregationDTO {
|
||||
/**
|
||||
* @type array
|
||||
@@ -3666,7 +3667,11 @@ export interface Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTyp
|
||||
* @type array
|
||||
*/
|
||||
selectFields?: TelemetrytypesTelemetryFieldKeyDTO[];
|
||||
signal?: TelemetrytypesSignalDTO;
|
||||
/**
|
||||
* @enum metrics
|
||||
* @type string
|
||||
*/
|
||||
signal: Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregationDTOSignal;
|
||||
source?: TelemetrytypesSourceDTO;
|
||||
stepInterval?: Querybuildertypesv5StepDTO;
|
||||
}
|
||||
@@ -3682,6 +3687,9 @@ export interface Querybuildertypesv5TraceAggregationDTO {
|
||||
expression?: string;
|
||||
}
|
||||
|
||||
export enum Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregationDTOSignal {
|
||||
traces = 'traces',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregationDTO {
|
||||
/**
|
||||
* @type array
|
||||
@@ -3734,7 +3742,11 @@ export interface Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTyp
|
||||
* @type array
|
||||
*/
|
||||
selectFields?: TelemetrytypesTelemetryFieldKeyDTO[];
|
||||
signal?: TelemetrytypesSignalDTO;
|
||||
/**
|
||||
* @enum traces
|
||||
* @type string
|
||||
*/
|
||||
signal: Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregationDTOSignal;
|
||||
source?: TelemetrytypesSourceDTO;
|
||||
stepInterval?: Querybuildertypesv5StepDTO;
|
||||
}
|
||||
@@ -3869,6 +3881,17 @@ export type DashboardtypesDashboardSpecDTODatasources = {
|
||||
export enum DashboardtypesPanelKindDTO {
|
||||
Panel = 'Panel',
|
||||
}
|
||||
export interface DashboardtypesDisplayDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export enum DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTimeSeriesPanelSpecDTOKind {
|
||||
'signoz/TimeSeriesPanel' = 'signoz/TimeSeriesPanel',
|
||||
}
|
||||
@@ -4417,42 +4440,36 @@ export interface DashboardtypesQuerySpecDTO {
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
plugin?: DashboardtypesQueryPluginDTO;
|
||||
plugin: DashboardtypesQueryPluginDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesQueryDTO {
|
||||
kind?: Querybuildertypesv5RequestTypeDTO;
|
||||
spec?: DashboardtypesQuerySpecDTO;
|
||||
kind: Querybuildertypesv5RequestTypeDTO;
|
||||
spec: DashboardtypesQuerySpecDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesPanelSpecDTO {
|
||||
display?: DashboardPanelDisplayDTO;
|
||||
display: DashboardtypesDisplayDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
links?: DashboardLinkDTO[];
|
||||
plugin?: DashboardtypesPanelPluginDTO;
|
||||
plugin: DashboardtypesPanelPluginDTO;
|
||||
/**
|
||||
* @type array
|
||||
* @type array,null
|
||||
*/
|
||||
queries?: DashboardtypesQueryDTO[];
|
||||
queries: DashboardtypesQueryDTO[] | null;
|
||||
}
|
||||
|
||||
export interface DashboardtypesPanelDTO {
|
||||
kind?: DashboardtypesPanelKindDTO;
|
||||
spec?: DashboardtypesPanelSpecDTO;
|
||||
kind: DashboardtypesPanelKindDTO;
|
||||
spec: DashboardtypesPanelSpecDTO;
|
||||
}
|
||||
|
||||
export type DashboardtypesDashboardSpecDTOPanelsAnyOf = {
|
||||
export type DashboardtypesDashboardSpecDTOPanels = {
|
||||
[key: string]: DashboardtypesPanelDTO;
|
||||
};
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type DashboardtypesDashboardSpecDTOPanels =
|
||||
DashboardtypesDashboardSpecDTOPanelsAnyOf | null;
|
||||
|
||||
export enum DashboardtypesLayoutEnvelopeGithubComPersesSpecGoDashboardGridLayoutSpecDTOKind {
|
||||
Grid = 'Grid',
|
||||
}
|
||||
@@ -4549,7 +4566,7 @@ export interface DashboardtypesListVariableSpecDTO {
|
||||
*/
|
||||
customAllValue?: string;
|
||||
defaultValue?: VariableDefaultValueDTO;
|
||||
display?: VariableDisplayDTO;
|
||||
display: DashboardtypesDisplayDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -4591,23 +4608,23 @@ export interface DashboardtypesDashboardSpecDTO {
|
||||
* @type object
|
||||
*/
|
||||
datasources?: DashboardtypesDashboardSpecDTODatasources;
|
||||
display?: CommonDisplayDTO;
|
||||
display: DashboardtypesDisplayDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
duration?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
layouts?: DashboardtypesLayoutDTO[] | null;
|
||||
layouts: DashboardtypesLayoutDTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
links?: DashboardLinkDTO[];
|
||||
/**
|
||||
* @type object,null
|
||||
* @type object
|
||||
*/
|
||||
panels?: DashboardtypesDashboardSpecDTOPanels;
|
||||
panels: DashboardtypesDashboardSpecDTOPanels;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -4615,13 +4632,13 @@ export interface DashboardtypesDashboardSpecDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
variables?: DashboardtypesVariableDTO[];
|
||||
variables: DashboardtypesVariableDTO[];
|
||||
}
|
||||
|
||||
export enum DashboardtypesDatasourcePluginKindDTO {
|
||||
'signoz/Datasource' = 'signoz/Datasource',
|
||||
}
|
||||
export interface TagtypesPostableTagDTO {
|
||||
export interface TagtypesGettableTagDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -4671,7 +4688,7 @@ export interface DashboardtypesGettableDashboardV2DTO {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
tags: TagtypesPostableTagDTO[] | null;
|
||||
tags: TagtypesGettableTagDTO[] | null;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
@@ -4729,6 +4746,157 @@ export interface DashboardtypesJSONPatchOperationDTO {
|
||||
value?: unknown;
|
||||
}
|
||||
|
||||
export enum DashboardtypesListOrderDTO {
|
||||
asc = 'asc',
|
||||
desc = 'desc',
|
||||
}
|
||||
export enum DashboardtypesListSortDTO {
|
||||
updated_at = 'updated_at',
|
||||
created_at = 'created_at',
|
||||
name = 'name',
|
||||
}
|
||||
export interface DashboardtypesListedDashboardV2SpecDTO {
|
||||
display?: DashboardtypesDisplayDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesListedDashboardForUserV2DTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
createdBy?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
image?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
locked: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
pinned: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
schemaVersion: string;
|
||||
source: DashboardtypesSourceDTO;
|
||||
spec: DashboardtypesListedDashboardV2SpecDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
tags: TagtypesGettableTagDTO[];
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export interface DashboardtypesListableDashboardForUserV2DTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
dashboards: DashboardtypesListedDashboardForUserV2DTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
tags: TagtypesGettableTagDTO[];
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface DashboardtypesListedDashboardV2DTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
createdBy?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
image?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
locked: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
schemaVersion: string;
|
||||
source: DashboardtypesSourceDTO;
|
||||
spec: DashboardtypesListedDashboardV2SpecDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
tags: TagtypesGettableTagDTO[];
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export interface DashboardtypesListableDashboardV2DTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
dashboards: DashboardtypesListedDashboardV2DTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
tags: TagtypesGettableTagDTO[];
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
total: number;
|
||||
}
|
||||
|
||||
export enum DashboardtypesPanelPluginKindDTO {
|
||||
'signoz/TimeSeriesPanel' = 'signoz/TimeSeriesPanel',
|
||||
'signoz/BarChartPanel' = 'signoz/BarChartPanel',
|
||||
@@ -4745,6 +4913,17 @@ export type DashboardtypesPatchableDashboardV2DTO =
|
||||
| DashboardtypesJSONPatchOperationDTO[]
|
||||
| null;
|
||||
|
||||
export interface TagtypesPostableTagDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface DashboardtypesPostableDashboardV2DTO {
|
||||
/**
|
||||
* @type boolean
|
||||
@@ -8093,10 +8272,6 @@ export interface SpantypesWaterfallSpanDTO {
|
||||
}
|
||||
|
||||
export interface SpantypesGettableWaterfallTraceDTO {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
aggregations?: SpantypesSpanAggregationResultDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
@@ -8216,15 +8391,6 @@ export interface SpantypesPostableTraceAggregationsDTO {
|
||||
}
|
||||
|
||||
export interface SpantypesPostableWaterfallDTO {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
aggregations?: SpantypesSpanAggregationDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -9660,6 +9826,40 @@ export type GetUserPreference200 = {
|
||||
export type UpdateUserPreferencePathParameters = {
|
||||
name: string;
|
||||
};
|
||||
export type ListDashboardsV2Params = {
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
query?: string;
|
||||
/**
|
||||
* @description undefined
|
||||
*/
|
||||
sort?: DashboardtypesListSortDTO;
|
||||
/**
|
||||
* @description undefined
|
||||
*/
|
||||
order?: DashboardtypesListOrderDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export type ListDashboardsV2200 = {
|
||||
data: DashboardtypesListableDashboardV2DTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateDashboardV2201 = {
|
||||
data: DashboardtypesGettableDashboardV2DTO;
|
||||
/**
|
||||
@@ -9668,6 +9868,9 @@ export type CreateDashboardV2201 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteDashboardV2PathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetDashboardV2PathParameters = {
|
||||
id: string;
|
||||
};
|
||||
@@ -10500,6 +10703,46 @@ export type GetMyUser200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListDashboardsForUserV2Params = {
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
query?: string;
|
||||
/**
|
||||
* @description undefined
|
||||
*/
|
||||
sort?: DashboardtypesListSortDTO;
|
||||
/**
|
||||
* @description undefined
|
||||
*/
|
||||
order?: DashboardtypesListOrderDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export type ListDashboardsForUserV2200 = {
|
||||
data: DashboardtypesListableDashboardForUserV2DTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type UnpinDashboardV2PathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type PinDashboardV2PathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetHosts200 = {
|
||||
data: ZeustypesGettableHostDTO;
|
||||
/**
|
||||
@@ -10519,17 +10762,6 @@ export type GetFlamegraph200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetWaterfallPathParameters = {
|
||||
traceID: string;
|
||||
};
|
||||
export type GetWaterfall200 = {
|
||||
data: SpantypesGettableWaterfallTraceDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetWaterfallV4PathParameters = {
|
||||
traceID: string;
|
||||
};
|
||||
|
||||
@@ -16,8 +16,6 @@ import type {
|
||||
GetFlamegraphPathParameters,
|
||||
GetTraceAggregations200,
|
||||
GetTraceAggregationsPathParameters,
|
||||
GetWaterfall200,
|
||||
GetWaterfallPathParameters,
|
||||
GetWaterfallV4200,
|
||||
GetWaterfallV4PathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
@@ -228,105 +226,6 @@ export const useGetFlamegraph = <
|
||||
> => {
|
||||
return useMutation(getGetFlamegraphMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* 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,
|
||||
spantypesPostableWaterfallDTO?: BodyType<SpantypesPostableWaterfallDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetWaterfall200>({
|
||||
url: `/api/v3/traces/${traceID}/waterfall`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: spantypesPostableWaterfallDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetWaterfallMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof getWaterfall>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallPathParameters;
|
||||
data?: BodyType<SpantypesPostableWaterfallDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof getWaterfall>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallPathParameters;
|
||||
data?: BodyType<SpantypesPostableWaterfallDTO>;
|
||||
},
|
||||
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<SpantypesPostableWaterfallDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return getWaterfall(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type GetWaterfallMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getWaterfall>>
|
||||
>;
|
||||
export type GetWaterfallMutationBody =
|
||||
| BodyType<SpantypesPostableWaterfallDTO>
|
||||
| undefined;
|
||||
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<SpantypesPostableWaterfallDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof getWaterfall>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallPathParameters;
|
||||
data?: BodyType<SpantypesPostableWaterfallDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getGetWaterfallMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* Returns the waterfall view of spans including all spans if total spans are under a limit, a max count otherwise. Aggregations are dropped compared to v3
|
||||
* @summary Get waterfall view for a trace
|
||||
|
||||
@@ -5,6 +5,13 @@ import {
|
||||
QueryKeySuggestionsResponseProps,
|
||||
} from 'types/api/querySuggestions/types';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetFieldsKeys` hook (or `getFieldsKeys` fetcher) from
|
||||
* `api/generated/services/fields` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
export const getKeySuggestions = (
|
||||
props: QueryKeyRequestProps,
|
||||
): Promise<AxiosResponse<QueryKeySuggestionsResponseProps>> => {
|
||||
|
||||
@@ -5,6 +5,13 @@ import {
|
||||
QueryKeyValueSuggestionsResponseProps,
|
||||
} from 'types/api/querySuggestions/types';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetFieldsValues` hook (or `getFieldsValues` fetcher) from
|
||||
* `api/generated/services/fields` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
export const getValueSuggestions = (
|
||||
props: QueryKeyValueRequestProps,
|
||||
): Promise<AxiosResponse<QueryKeyValueSuggestionsResponseProps>> => {
|
||||
|
||||
@@ -15,6 +15,13 @@ export interface CreateRoutingPolicyResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateRoutePolicy` hook (or `createRoutePolicy` fetcher) from
|
||||
* `api/generated/services/routepolicies` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const createRoutingPolicy = async (
|
||||
props: CreateRoutingPolicyBody,
|
||||
): Promise<
|
||||
|
||||
@@ -8,6 +8,13 @@ export interface DeleteRoutingPolicyResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useDeleteRoutePolicyByID` hook (or `deleteRoutePolicyByID` fetcher) from
|
||||
* `api/generated/services/routepolicies` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const deleteRoutingPolicy = async (
|
||||
routingPolicyId: string,
|
||||
): Promise<
|
||||
|
||||
@@ -20,6 +20,13 @@ export interface GetRoutingPoliciesResponse {
|
||||
data?: ApiRoutingPolicy[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetAllRoutePolicies` hook (or `getAllRoutePolicies` fetcher) from
|
||||
* `api/generated/services/routepolicies` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
export const getRoutingPolicies = async (
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
|
||||
@@ -15,6 +15,13 @@ export interface UpdateRoutingPolicyResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateRoutePolicy` hook (or `updateRoutePolicy` fetcher) from
|
||||
* `api/generated/services/routepolicies` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const updateRoutingPolicy = async (
|
||||
id: string,
|
||||
props: UpdateRoutingPolicyBody,
|
||||
|
||||
@@ -27,7 +27,6 @@ const getTraceV4 = async (
|
||||
{
|
||||
selectedSpanId: props.selectedSpanId,
|
||||
uncollapsedSpans,
|
||||
limit: 10000,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/resetPassword';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useResetPassword` hook (or `resetPassword` fetcher) from
|
||||
* `api/generated/services/users` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const resetPassword = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { UsersProps } from 'types/api/user/inviteUsers';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateBulkInvite` hook (or `createBulkInvite` fetcher) from
|
||||
* `api/generated/services/users` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const inviteUsers = async (
|
||||
users: UsersProps,
|
||||
): Promise<SuccessResponseV2<null>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/setInvite';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateInvite` hook (or `createInvite` fetcher) from
|
||||
* `api/generated/services/users` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const sendInvite = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
|
||||
@@ -5,6 +5,13 @@ import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/preferences/list';
|
||||
import { OrgPreference } from 'types/api/preferences/preference';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useListOrgPreferences` hook (or `listOrgPreferences` fetcher) from
|
||||
* `api/generated/services/preferences` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const listPreference = async (): Promise<
|
||||
SuccessResponseV2<OrgPreference[]>
|
||||
> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { Props } from 'types/api/preferences/update';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateOrgPreference` hook (or `updateOrgPreference` fetcher) from
|
||||
* `api/generated/services/preferences` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const update = async (props: Props): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.put(`/org/preferences/${props.name}`, {
|
||||
|
||||
@@ -5,6 +5,13 @@ import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/preferences/list';
|
||||
import { UserPreference } from 'types/api/preferences/preference';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useListUserPreferences` hook (or `listUserPreferences` fetcher) from
|
||||
* `api/generated/services/preferences` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const list = async (): Promise<SuccessResponseV2<UserPreference[]>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/user/preferences`);
|
||||
|
||||
@@ -5,6 +5,13 @@ import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/preferences/get';
|
||||
import { UserPreference } from 'types/api/preferences/preference';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetUserPreference` hook (or `getUserPreference` fetcher) from
|
||||
* `api/generated/services/preferences` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const get = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<UserPreference>> => {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { Props } from 'types/api/preferences/update';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateUserPreference` hook (or `updateUserPreference` fetcher) from
|
||||
* `api/generated/services/preferences` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const update = async (props: Props): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.put(`/user/preferences/${props.name}`, {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, RawSuccessResponse, SuccessResponseV2 } from 'types/api';
|
||||
import { Props, SessionsContext } from 'types/api/v2/sessions/context/get';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetSessionContext` hook (or `getSessionContext` fetcher) from
|
||||
* `api/generated/services/sessions` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const get = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<SessionsContext>> => {
|
||||
|
||||
@@ -3,6 +3,13 @@ import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, RawSuccessResponse, SuccessResponseV2 } from 'types/api';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useDeleteSession` hook (or `deleteSession` fetcher) from
|
||||
* `api/generated/services/sessions` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const deleteSession = async (): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.delete<RawSuccessResponse<null>>('/sessions');
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, RawSuccessResponse, SuccessResponseV2 } from 'types/api';
|
||||
import { Props, Token } from 'types/api/v2/sessions/email_password/post';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateSessionByEmailPassword` hook (or `createSessionByEmailPassword` fetcher) from
|
||||
* `api/generated/services/sessions` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const post = async (props: Props): Promise<SuccessResponseV2<Token>> => {
|
||||
try {
|
||||
const response = await axios.post<RawSuccessResponse<Token>>(
|
||||
|
||||
@@ -4,6 +4,13 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, RawSuccessResponse, SuccessResponseV2 } from 'types/api';
|
||||
import { Props, Token } from 'types/api/v2/sessions/rotate/post';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useRotateSession` hook (or `rotateSession` fetcher) from
|
||||
* `api/generated/services/sessions` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const post = async (props: Props): Promise<SuccessResponseV2<Token>> => {
|
||||
try {
|
||||
const response = await axios.post<RawSuccessResponse<Token>>(
|
||||
|
||||
@@ -8,6 +8,13 @@ import {
|
||||
QueryRangePayloadV5,
|
||||
} from 'types/api/v5/queryRange';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useQueryRangeV5` hook (or `queryRangeV5` fetcher) from
|
||||
* `api/generated/services/querier` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
export const getQueryRangeV5 = async (
|
||||
props: QueryRangePayloadV5,
|
||||
version: string,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
$dropdown-base-height: 250px;
|
||||
$recents-header-height: 30px;
|
||||
$recent-row-height: 36px;
|
||||
// how many recents are rendered, this caps how tall the dropdown can grow to fit them.
|
||||
$max-recents-shown: 5;
|
||||
|
||||
.code-mirror-where-clause {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -117,7 +123,23 @@
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
min-height: 200px !important;
|
||||
max-height: $dropdown-base-height !important;
|
||||
overflow-y: auto !important;
|
||||
|
||||
// Recents render at the top of the dropdown ahead of regular suggestions.
|
||||
// At the base max-height, having recents in view would crowd out the
|
||||
// suggestion list below. This loop grows the dropdown by one row's worth
|
||||
// of height for every recent present (up to $max-recents-shown), plus the
|
||||
// section header. `:has(> li:nth-of-type(N) .cm-completionIcon-recent)`
|
||||
// matches when the Nth child of <ul> is a recent — i.e. there are at
|
||||
// least N recents visible.
|
||||
@for $i from 1 through $max-recents-shown {
|
||||
&:has(> li:nth-of-type(#{$i}) .cm-completionIcon-recent) {
|
||||
max-height: $dropdown-base-height +
|
||||
$recents-header-height +
|
||||
($i * $recent-row-height) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.3rem;
|
||||
@@ -133,6 +155,19 @@
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
completion-section {
|
||||
display: block;
|
||||
padding: 10px 12px 6px;
|
||||
font-size: 10px !important;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--l3-foreground, var(--l2-foreground));
|
||||
opacity: 0.7;
|
||||
border-bottom: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
li {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
@@ -159,11 +194,78 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.cm-completionDetail {
|
||||
margin-left: auto;
|
||||
font-style: normal;
|
||||
font-size: var(--periscope-font-size-small, 11px);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
background: var(--l3-background) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
|
||||
li:has(.cm-completionIcon-recent) {
|
||||
&:hover .cm-recent-delete,
|
||||
&[aria-selected='true'] .cm-recent-delete {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
li .cm-completionLabel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.cm-recent-delete {
|
||||
margin-left: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--l2-foreground);
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
transition:
|
||||
opacity 0.12s ease,
|
||||
background-color 0.12s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background: color-mix(in srgb, var(--l2-foreground) 18%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '↓↑ to navigate · ↵ to apply · esc to dismiss';
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
border-top: 1px solid var(--l1-border);
|
||||
font-family: 'Space Mono', monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--l2-foreground);
|
||||
opacity: 0.6;
|
||||
background: color-mix(in srgb, var(--l1-background) 50%, transparent);
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,8 +46,15 @@ import {
|
||||
import { validateQuery } from 'utils/queryValidationUtils';
|
||||
import { unquote } from 'utils/stringUtils';
|
||||
|
||||
import { queryExamples } from './constants';
|
||||
import { combineInitialAndUserExpression } from './utils';
|
||||
import { getRecentQueries } from 'lib/recentQueries/getRecentQueries';
|
||||
import type { SignalType } from 'types/api/v5/queryRange';
|
||||
|
||||
import { queryExamples, SUGGESTIONS_SECTION } from './constants';
|
||||
import {
|
||||
combineInitialAndUserExpression,
|
||||
getRecentOptions,
|
||||
renderRecentDeleteButton,
|
||||
} from './utils';
|
||||
|
||||
import './QuerySearch.styles.scss';
|
||||
|
||||
@@ -1250,6 +1257,41 @@ function QuerySearch({
|
||||
};
|
||||
}
|
||||
|
||||
const signal = dataSource as SignalType;
|
||||
|
||||
function combinedSuggestions(
|
||||
context: CompletionContext,
|
||||
): CompletionResult | null {
|
||||
const fullDoc = context.state.doc.toString();
|
||||
const recentOptions = getRecentOptions(
|
||||
getRecentQueries(signal, signalSource ?? ''),
|
||||
fullDoc,
|
||||
);
|
||||
const result = autoSuggestions(context);
|
||||
|
||||
const suggestionOptions = (result?.options || []).map((opt) => ({
|
||||
...opt,
|
||||
section: SUGGESTIONS_SECTION,
|
||||
}));
|
||||
|
||||
if (recentOptions.length === 0 && suggestionOptions.length === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return {
|
||||
from: 0,
|
||||
to: fullDoc.length,
|
||||
options: recentOptions,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...result,
|
||||
options: [...recentOptions, ...suggestionOptions],
|
||||
};
|
||||
}
|
||||
|
||||
// Effect to handle focus state and trigger suggestions
|
||||
useEffect(() => {
|
||||
const clearTimeout = toggleSuggestions(10);
|
||||
@@ -1398,11 +1440,12 @@ function QuerySearch({
|
||||
})}
|
||||
extensions={[
|
||||
autocompletion({
|
||||
override: [autoSuggestions],
|
||||
override: [combinedSuggestions],
|
||||
defaultKeymap: true,
|
||||
closeOnBlur: true,
|
||||
activateOnTyping: true,
|
||||
maxRenderedOptions: 50,
|
||||
addToOptions: [{ render: renderRecentDeleteButton, position: 100 }],
|
||||
}),
|
||||
javascript({ jsx: false, typescript: false }),
|
||||
EditorView.lineWrapping,
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
export const RECENTS_SECTION = { name: 'Recent searches', rank: 1 } as const;
|
||||
export const SUGGESTIONS_SECTION = { name: 'Suggestions', rank: 2 } as const;
|
||||
|
||||
// Custom CodeMirror Completion.type for recent-query entries. Used to discriminate
|
||||
// recents from regular autocomplete completions in renderers and event handlers.
|
||||
export const RECENT_COMPLETION_TYPE = 'recent';
|
||||
|
||||
// Max number of recents rendered in the autocomplete dropdown.
|
||||
// Do change $max-recents-shown: in QuerySearch.styles.scss if you change this.
|
||||
export const RECENTS_DISPLAY_CAP = 5;
|
||||
|
||||
export const queryExamples = [
|
||||
{
|
||||
label: 'Basic Query',
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
import { closeCompletion, startCompletion } from '@codemirror/autocomplete';
|
||||
import type { Completion } from '@codemirror/autocomplete';
|
||||
import type { EditorView } from '@uiw/react-codemirror';
|
||||
import dayjs from 'dayjs';
|
||||
import { normalizeFilterExpression } from 'lib/recentQueries/normalize';
|
||||
import * as recentQueriesStore from 'lib/recentQueries/recentQueriesStore';
|
||||
import type { RecentQueryEntry } from 'lib/recentQueries/types';
|
||||
import type { SignalType } from 'types/api/v5/queryRange';
|
||||
import 'utils/timeUtils';
|
||||
|
||||
import {
|
||||
RECENT_COMPLETION_TYPE,
|
||||
RECENTS_DISPLAY_CAP,
|
||||
RECENTS_SECTION,
|
||||
} from './constants';
|
||||
|
||||
export function combineInitialAndUserExpression(
|
||||
initial: string,
|
||||
user: string,
|
||||
@@ -38,3 +54,106 @@ export function getUserExpressionFromCombined(
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// Filters and projects a list of recent-query entries into CodeMirror completions.
|
||||
// Entries are supplied by the caller (typically via the useRecents hook) so this
|
||||
// function stays pure and React doesn't have to re-subscribe inside CodeMirror's
|
||||
// autocomplete callback.
|
||||
export function getRecentOptions(
|
||||
entries: RecentQueryEntry[],
|
||||
fullDoc: string,
|
||||
): Completion[] {
|
||||
const normalizedDoc = normalizeFilterExpression(fullDoc);
|
||||
|
||||
const matches = entries
|
||||
.filter((e) => {
|
||||
const normalizedRecent = normalizeFilterExpression(e.filter.expression);
|
||||
if (normalizedRecent === normalizedDoc) {
|
||||
return false;
|
||||
}
|
||||
if (normalizedDoc === '') {
|
||||
return true;
|
||||
}
|
||||
return normalizedRecent.includes(normalizedDoc);
|
||||
})
|
||||
.slice(0, RECENTS_DISPLAY_CAP);
|
||||
|
||||
return matches.map((entry, index) => ({
|
||||
label: entry.filter.expression,
|
||||
type: RECENT_COMPLETION_TYPE,
|
||||
// CodeMirror sorts within a section by boost desc, then label asc. The store
|
||||
// returns entries newest-first, so we mirror that by giving the newest entry
|
||||
// the highest boost — otherwise CM falls back to alphabetical order and the
|
||||
// "most recently used" expectation breaks. Stays within the recents section
|
||||
// because section.rank keeps recents above suggestions regardless of boost.
|
||||
boost: matches.length - index,
|
||||
section: RECENTS_SECTION,
|
||||
detail: dayjs(entry.lastUsedAt).fromNow(),
|
||||
recentId: entry.id,
|
||||
recentSignal: entry.signal,
|
||||
recentSource: entry.source,
|
||||
apply: (view: EditorView): void => {
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: view.state.doc.length,
|
||||
insert: entry.filter.expression,
|
||||
},
|
||||
selection: { anchor: entry.filter.expression.length },
|
||||
});
|
||||
closeCompletion(view);
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
export function renderRecentDeleteButton(
|
||||
completion: Completion,
|
||||
_state: unknown,
|
||||
view: EditorView | null,
|
||||
): Node | null {
|
||||
if (completion.type !== RECENT_COMPLETION_TYPE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const c = completion as Completion & {
|
||||
recentId?: string;
|
||||
recentSignal?: SignalType;
|
||||
recentSource?: string;
|
||||
};
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'cm-recent-delete';
|
||||
btn.setAttribute('aria-label', 'Remove from recent searches');
|
||||
btn.title = 'Remove from recent searches';
|
||||
btn.textContent = '×';
|
||||
queueMicrotask(() => {
|
||||
if (btn.parentElement) {
|
||||
btn.parentElement.title = completion.label;
|
||||
}
|
||||
});
|
||||
|
||||
const stop = (e: Event): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
// CodeMirror's autocomplete closes the popup on pointerdown / mousedown outside
|
||||
// the editor. The delete button lives inside the popup, so we must stop those
|
||||
// events early — otherwise clicking × would dismiss the dropdown before the
|
||||
// click handler fires and the entry wouldn't actually get removed.
|
||||
btn.addEventListener('pointerdown', stop);
|
||||
btn.addEventListener('mousedown', stop);
|
||||
btn.addEventListener('click', (e) => {
|
||||
stop(e);
|
||||
if (!c.recentId || !c.recentSignal) {
|
||||
return;
|
||||
}
|
||||
recentQueriesStore.remove(c.recentId, c.recentSignal, c.recentSource ?? '');
|
||||
if (view) {
|
||||
view.focus();
|
||||
startCompletion(view);
|
||||
}
|
||||
});
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ export const AIAssistantOpenSource = {
|
||||
Icon: 'icon',
|
||||
Shortcut: 'shortcut',
|
||||
Cmdk: 'cmdk',
|
||||
TraceDetails: 'trace_details',
|
||||
} as const;
|
||||
export type AIAssistantOpenSource =
|
||||
(typeof AIAssistantOpenSource)[keyof typeof AIAssistantOpenSource];
|
||||
|
||||
@@ -40,13 +40,31 @@ type SpeechRecognitionConstructor = new () => ISpeechRecognition;
|
||||
|
||||
// ── Vendor-prefix shim for Safari / older browsers ────────────────────────────
|
||||
|
||||
const SpeechRecognitionAPI: SpeechRecognitionConstructor | null =
|
||||
typeof window !== 'undefined'
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
((window as any).SpeechRecognition ??
|
||||
// Some hardened/enterprise browsers install a getter
|
||||
// on window.SpeechRecognition that THROWS on access ("Web Speech API is disabled
|
||||
// due to your security policy") instead of leaving the property undefined.
|
||||
// Because this resolves at module-evaluation time, an uncaught throw here aborts
|
||||
// the entire bundle and the app renders a blank page. Read defensively so a
|
||||
// throwing getter degrades to "unsupported" rather than crashing the app.
|
||||
function resolveSpeechRecognitionAPI(): SpeechRecognitionConstructor | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).SpeechRecognition ??
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).webkitSpeechRecognition ??
|
||||
null)
|
||||
: null;
|
||||
null
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const SpeechRecognitionAPI: SpeechRecognitionConstructor | null =
|
||||
resolveSpeechRecognitionAPI();
|
||||
|
||||
export type SpeechRecognitionError =
|
||||
| 'not-supported'
|
||||
|
||||
@@ -67,3 +67,40 @@
|
||||
background: var(--secondary-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
.fallbackBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.fallbackHint {
|
||||
font-size: var(--paragraph-base-400-font-size);
|
||||
font-weight: var(--paragraph-base-400-font-weight);
|
||||
line-height: var(--paragraph-base-400-line-height);
|
||||
color: var(--l2-foreground);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fallbackEmail {
|
||||
font-size: var(--paragraph-base-500-font-size);
|
||||
font-weight: var(--paragraph-base-500-font-weight);
|
||||
color: var(--l1-foreground);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.fallbackActions {
|
||||
display: flex;
|
||||
gap: var(--spacing-3);
|
||||
flex-wrap: wrap;
|
||||
padding-top: var(--padding-4);
|
||||
}
|
||||
|
||||
.retryLink {
|
||||
box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,37 @@ jest.mock('utils/basePath', () => ({
|
||||
getBaseUrl: (): string => 'https://test.signoz.io',
|
||||
}));
|
||||
|
||||
function mockMailto(): {
|
||||
mockClick: jest.Mock;
|
||||
appendSpy: jest.SpyInstance;
|
||||
removeSpy: jest.SpyInstance;
|
||||
} {
|
||||
const mockClick = jest.fn();
|
||||
const realCreateElement = document.createElement.bind(document);
|
||||
|
||||
// Create a real anchor so JSDOM's appendChild/removeChild accept it.
|
||||
// Override its click() so no navigation occurs.
|
||||
jest
|
||||
.spyOn(document, 'createElement')
|
||||
.mockImplementation((tag: string, options?: ElementCreationOptions) => {
|
||||
if (tag === 'a') {
|
||||
const anchor = realCreateElement('a') as HTMLAnchorElement;
|
||||
anchor.click = mockClick;
|
||||
return anchor;
|
||||
}
|
||||
return realCreateElement(tag, options);
|
||||
});
|
||||
|
||||
const appendSpy = jest.spyOn(document.body, 'appendChild');
|
||||
const removeSpy = jest.spyOn(document.body, 'removeChild');
|
||||
return { mockClick, appendSpy, removeSpy };
|
||||
}
|
||||
|
||||
describe('CancelSubscriptionBanner', () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('renders banner with title and subtitle', () => {
|
||||
render(<CancelSubscriptionBanner />);
|
||||
expect(
|
||||
@@ -35,12 +65,10 @@ describe('CancelSubscriptionBanner', () => {
|
||||
screen.getByText(/Cancelling your subscription would stop your data/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/Type/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByPlaceholderText(/Enter the word cancel/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('cancel-confirm-input')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /go back/i })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('button', { name: /cancel subscription/i }),
|
||||
screen.getByTestId('cancel-subscription-confirm-btn'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -52,12 +80,10 @@ describe('CancelSubscriptionBanner', () => {
|
||||
screen.getByRole('button', { name: /cancel subscription/i }),
|
||||
);
|
||||
|
||||
const confirmButton = screen.getByRole('button', {
|
||||
name: /cancel subscription/i,
|
||||
});
|
||||
const confirmButton = screen.getByTestId('cancel-subscription-confirm-btn');
|
||||
expect(confirmButton).toBeDisabled();
|
||||
|
||||
const input = screen.getByPlaceholderText(/Enter the word cancel/i);
|
||||
const input = screen.getByTestId('cancel-confirm-input');
|
||||
await user.type(input, 'canc');
|
||||
expect(confirmButton).toBeDisabled();
|
||||
|
||||
@@ -73,7 +99,7 @@ describe('CancelSubscriptionBanner', () => {
|
||||
screen.getByRole('button', { name: /cancel subscription/i }),
|
||||
);
|
||||
|
||||
const input = screen.getByPlaceholderText(/Enter the word cancel/i);
|
||||
const input = screen.getByTestId('cancel-confirm-input');
|
||||
await user.type(input, 'cancel');
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /go back/i }));
|
||||
@@ -84,19 +110,11 @@ describe('CancelSubscriptionBanner', () => {
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /cancel subscription/i }),
|
||||
);
|
||||
expect(screen.getByPlaceholderText(/Enter the word cancel/i)).toHaveValue('');
|
||||
expect(screen.getByTestId('cancel-confirm-input')).toHaveValue('');
|
||||
});
|
||||
|
||||
it('sends mailto to cloud-support with correct subject after typing "cancel"', async () => {
|
||||
const realCreateElement = document.createElement.bind(document);
|
||||
const mockClick = jest.fn();
|
||||
const mockAnchor = { href: '', click: mockClick };
|
||||
jest.spyOn(document, 'createElement').mockImplementation((tag: string) => {
|
||||
if (tag === 'a') {
|
||||
return mockAnchor as unknown as HTMLAnchorElement;
|
||||
}
|
||||
return realCreateElement(tag);
|
||||
});
|
||||
it('fires mailto via DOM-attached anchor and shows fallback view after confirming', async () => {
|
||||
const { mockClick, appendSpy, removeSpy } = mockMailto();
|
||||
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CancelSubscriptionBanner />);
|
||||
@@ -104,18 +122,85 @@ describe('CancelSubscriptionBanner', () => {
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /cancel subscription/i }),
|
||||
);
|
||||
await user.type(screen.getByTestId('cancel-confirm-input'), 'cancel');
|
||||
await user.click(screen.getByTestId('cancel-subscription-confirm-btn'));
|
||||
|
||||
const input = screen.getByPlaceholderText(/Enter the word cancel/i);
|
||||
await user.type(input, 'cancel');
|
||||
const appendedAnchor = appendSpy.mock.calls
|
||||
.map(([node]) => node)
|
||||
.find(
|
||||
(node): node is HTMLAnchorElement =>
|
||||
node instanceof HTMLAnchorElement && node.href.startsWith('mailto:'),
|
||||
);
|
||||
expect(appendedAnchor).toBeDefined();
|
||||
expect(mockClick).toHaveBeenCalledTimes(1);
|
||||
expect(removeSpy.mock.calls.some(([node]) => node === appendedAnchor)).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText(/An email draft has been opened/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('cloud-support@signoz.io')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('copy-email-template-btn')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('retry-mailto-btn')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('copies email template to clipboard when Copy button is clicked', async () => {
|
||||
mockMailto();
|
||||
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CancelSubscriptionBanner />);
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /cancel subscription/i }),
|
||||
);
|
||||
await user.type(screen.getByTestId('cancel-confirm-input'), 'cancel');
|
||||
await user.click(screen.getByTestId('cancel-subscription-confirm-btn'));
|
||||
|
||||
expect(mockAnchor.href).toContain('mailto:cloud-support@signoz.io');
|
||||
expect(mockAnchor.href).toContain('Cancel%20My%20SigNoz%20Subscription');
|
||||
expect(mockClick).toHaveBeenCalledTimes(1);
|
||||
await user.click(screen.getByTestId('copy-email-template-btn'));
|
||||
|
||||
jest.restoreAllMocks();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('copy-email-template-btn')).toHaveTextContent(
|
||||
'Copied!',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('retry link is a native anchor with correct mailto href in fallback view', async () => {
|
||||
mockMailto();
|
||||
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CancelSubscriptionBanner />);
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /cancel subscription/i }),
|
||||
);
|
||||
await user.type(screen.getByTestId('cancel-confirm-input'), 'cancel');
|
||||
await user.click(screen.getByTestId('cancel-subscription-confirm-btn'));
|
||||
|
||||
const retryLink = screen.getByTestId('retry-mailto-btn');
|
||||
expect(retryLink.tagName).toBe('A');
|
||||
expect(retryLink).toHaveAttribute(
|
||||
'href',
|
||||
expect.stringContaining('mailto:cloud-support@signoz.io'),
|
||||
);
|
||||
});
|
||||
|
||||
it('closes fallback view when Close is clicked and resets state', async () => {
|
||||
mockMailto();
|
||||
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CancelSubscriptionBanner />);
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /cancel subscription/i }),
|
||||
);
|
||||
await user.type(screen.getByTestId('cancel-confirm-input'), 'cancel');
|
||||
await user.click(screen.getByTestId('cancel-subscription-confirm-btn'));
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /close/i }));
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,27 +1,100 @@
|
||||
import { useState } from 'react';
|
||||
import { SolidInfoCircle, Undo2, X } from '@signozhq/icons';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
CircleCheck,
|
||||
Copy,
|
||||
MailOpen,
|
||||
SolidInfoCircle,
|
||||
Undo2,
|
||||
X,
|
||||
} from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { DialogWrapper } from '@signozhq/ui/dialog';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { pick } from 'lodash-es';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
import styles from './CancelSubscriptionBanner.module.scss';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
|
||||
import styles from './CancelSubscriptionBanner.module.scss';
|
||||
|
||||
const SUPPORT_EMAIL = 'cloud-support@signoz.io';
|
||||
const MAX_MAILTO_URI_LENGTH = 1800;
|
||||
|
||||
type DialogView = 'confirm' | 'fallback';
|
||||
|
||||
function buildEmailBody(orgName: string, userEmail: string): string {
|
||||
return [
|
||||
'Hi SigNoz Team,',
|
||||
'',
|
||||
'I would like to cancel my SigNoz Cloud subscription.',
|
||||
'Please find my account details below.',
|
||||
'',
|
||||
'Account Details:',
|
||||
` • SigNoz URL: ${getBaseUrl()}`,
|
||||
...(orgName ? [` • Organization: ${orgName}`] : []),
|
||||
` • Account Email: ${userEmail}`,
|
||||
'',
|
||||
'Reason for Cancellation:',
|
||||
'[Please share the reason for cancellation]',
|
||||
'',
|
||||
'Additional feedback (optional):',
|
||||
'[Any other feedback]',
|
||||
'',
|
||||
'Regards,',
|
||||
'[user name or team name]',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function buildMailtoUri(orgName: string, userEmail: string): string {
|
||||
const subject = encodeURIComponent('Cancel My SigNoz Subscription');
|
||||
const body = encodeURIComponent(buildEmailBody(orgName, userEmail));
|
||||
const full = `mailto:${SUPPORT_EMAIL}?subject=${subject}&body=${body}`;
|
||||
if (full.length <= MAX_MAILTO_URI_LENGTH) {
|
||||
return full;
|
||||
}
|
||||
const shortBody = encodeURIComponent(
|
||||
'Hi SigNoz Team,\n\nI would like to cancel my SigNoz Cloud subscription.\nPlease find my account details and reason for cancellation below.\n\n[Your details here]\n\nRegards,',
|
||||
);
|
||||
return `mailto:${SUPPORT_EMAIL}?subject=${subject}&body=${shortBody}`;
|
||||
}
|
||||
|
||||
function openMailto(uri: string): void {
|
||||
const link = document.createElement('a');
|
||||
link.href = uri;
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
function CancelSubscriptionBanner(): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [dialogView, setDialogView] = useState<DialogView | null>(null);
|
||||
const [confirmText, setConfirmText] = useState('');
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
const copyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const { user, org } = useAppContext();
|
||||
|
||||
useEffect(
|
||||
() => (): void => {
|
||||
if (copyTimerRef.current) {
|
||||
clearTimeout(copyTimerRef.current);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const orgName = org?.[0]?.displayName ?? '';
|
||||
const userEmail = user?.email ?? '';
|
||||
|
||||
const handleOpenCancelDialog = (): void => {
|
||||
void logEvent('Billing : Cancel Subscription Clicked', {
|
||||
user: pick(user, ['email', 'displayName', 'role', 'organization']),
|
||||
role: user?.role,
|
||||
});
|
||||
setOpen(true);
|
||||
setDialogView('confirm');
|
||||
};
|
||||
|
||||
const handleContactSupport = (): void => {
|
||||
@@ -29,43 +102,41 @@ function CancelSubscriptionBanner(): JSX.Element {
|
||||
user: pick(user, ['email', 'displayName', 'role', 'organization']),
|
||||
role: user?.role,
|
||||
});
|
||||
const subject = encodeURIComponent('Cancel My SigNoz Subscription');
|
||||
const orgName = org?.[0]?.displayName ?? '';
|
||||
const body = encodeURIComponent(
|
||||
[
|
||||
'Hi SigNoz Team,',
|
||||
'',
|
||||
'I would like to cancel my SigNoz Cloud subscription.',
|
||||
'Please find my account details below.',
|
||||
'',
|
||||
'Account Details:',
|
||||
` • SigNoz URL: ${getBaseUrl()}`,
|
||||
...(orgName ? [` • Organization: ${orgName}`] : []),
|
||||
` • Account Email: ${user?.email ?? ''}`,
|
||||
'',
|
||||
'Reason for Cancellation:',
|
||||
'[Please share the reason for cancellation]',
|
||||
'',
|
||||
'Additional feedback (optional):',
|
||||
'[Any other feedback]',
|
||||
'',
|
||||
'Regards,',
|
||||
'[user name or team name]',
|
||||
].join('\n'),
|
||||
);
|
||||
const link = document.createElement('a');
|
||||
link.href = `mailto:cloud-support@signoz.io?subject=${subject}&body=${body}`;
|
||||
link.click();
|
||||
setOpen(false);
|
||||
openMailto(buildMailtoUri(orgName, userEmail));
|
||||
setConfirmText('');
|
||||
setDialogView('fallback');
|
||||
};
|
||||
|
||||
const handleCopyTemplate = (): void => {
|
||||
void logEvent('Billing : Cancel Subscription Email Template Copied', {
|
||||
user: pick(user, ['email', 'displayName', 'role', 'organization']),
|
||||
role: user?.role,
|
||||
});
|
||||
copyToClipboard(buildEmailBody(orgName, userEmail));
|
||||
setCopied(true);
|
||||
if (copyTimerRef.current) {
|
||||
clearTimeout(copyTimerRef.current);
|
||||
}
|
||||
copyTimerRef.current = setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
const handleRetryMailto = (): void => {
|
||||
void logEvent('Billing : Cancel Subscription Email Client Reopened', {
|
||||
user: pick(user, ['email', 'displayName', 'role', 'organization']),
|
||||
role: user?.role,
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = (): void => {
|
||||
setOpen(false);
|
||||
if (copyTimerRef.current) {
|
||||
clearTimeout(copyTimerRef.current);
|
||||
}
|
||||
setDialogView(null);
|
||||
setConfirmText('');
|
||||
setCopied(false);
|
||||
};
|
||||
|
||||
const footer = (
|
||||
const confirmFooter = (
|
||||
<>
|
||||
<Button
|
||||
variant="solid"
|
||||
@@ -81,12 +152,19 @@ function CancelSubscriptionBanner(): JSX.Element {
|
||||
prefix={<X size={14} />}
|
||||
disabled={confirmText !== 'cancel'}
|
||||
onClick={handleContactSupport}
|
||||
data-testid="cancel-subscription-confirm-btn"
|
||||
>
|
||||
Cancel subscription
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
||||
const fallbackFooter = (
|
||||
<Button variant="solid" color="secondary" onClick={handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.banner}>
|
||||
@@ -111,27 +189,67 @@ function CancelSubscriptionBanner(): JSX.Element {
|
||||
</Button>
|
||||
</div>
|
||||
<DialogWrapper
|
||||
open={open}
|
||||
open={dialogView !== null}
|
||||
onOpenChange={handleClose}
|
||||
title="Cancel your subscription?"
|
||||
width="narrow"
|
||||
showCloseButton={false}
|
||||
footer={footer}
|
||||
footer={dialogView === 'confirm' ? confirmFooter : fallbackFooter}
|
||||
>
|
||||
<div className={styles.dialogBody}>
|
||||
<p className={styles.dialogDescription}>
|
||||
Cancelling your subscription would stop your data from being ingested to
|
||||
SigNoz. All the data that has been already sent will also be deleted.
|
||||
</p>
|
||||
<p className={styles.dialogConfirmLabel}>
|
||||
Type <code>cancel</code> to confirm the cancellation.
|
||||
</p>
|
||||
<Input
|
||||
placeholder="Enter the word cancel..."
|
||||
value={confirmText}
|
||||
onChange={(e): void => setConfirmText(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{dialogView === 'confirm' && (
|
||||
<div className={styles.dialogBody}>
|
||||
<p className={styles.dialogDescription}>
|
||||
Cancelling your subscription would stop your data from being ingested to
|
||||
SigNoz. All the data that has been already sent will also be deleted.
|
||||
</p>
|
||||
<p className={styles.dialogConfirmLabel}>
|
||||
Type <code>cancel</code> to confirm the cancellation.
|
||||
</p>
|
||||
<Input
|
||||
placeholder="Enter the word cancel..."
|
||||
value={confirmText}
|
||||
onChange={(e): void => setConfirmText(e.target.value)}
|
||||
data-testid="cancel-confirm-input"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{dialogView === 'fallback' && (
|
||||
<div className={styles.fallbackBody}>
|
||||
<p className={styles.fallbackHint}>
|
||||
An email draft has been opened. If it did not open, send your
|
||||
cancellation request directly to:
|
||||
</p>
|
||||
<span className={styles.fallbackEmail}>{SUPPORT_EMAIL}</span>
|
||||
<div className={styles.fallbackActions}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={copied ? <CircleCheck size={14} /> : <Copy size={14} />}
|
||||
onClick={handleCopyTemplate}
|
||||
data-testid="copy-email-template-btn"
|
||||
>
|
||||
{copied ? 'Copied!' : 'Copy email template'}
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
data-testid="retry-mailto-btn"
|
||||
>
|
||||
<a
|
||||
href={buildMailtoUri(orgName, userEmail)}
|
||||
onClick={handleRetryMailto}
|
||||
className={styles.retryLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<MailOpen size={14} />
|
||||
Reopen email client
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -142,6 +142,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.reset-password-back-action {
|
||||
margin-top: var(--spacing-12);
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { CircleAlert } from '@signozhq/icons';
|
||||
import { ArrowLeft, CircleAlert } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import AuthError from 'components/AuthError/AuthError';
|
||||
import AuthPageContainer from 'components/AuthPageContainer';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import './ResetPassword.styles.scss';
|
||||
@@ -59,6 +62,16 @@ function TokenError({ error }: TokenErrorProps): JSX.Element {
|
||||
</Typography.Text>
|
||||
</div>
|
||||
{error && <AuthError error={error} />}
|
||||
<div className="reset-password-back-action">
|
||||
<Button
|
||||
variant="solid"
|
||||
data-testid="back-to-login"
|
||||
prefix={<ArrowLeft size={12} />}
|
||||
onClick={(): void => history.push(ROUTES.LOGIN)}
|
||||
>
|
||||
Back to login
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AuthPageContainer>
|
||||
);
|
||||
|
||||
@@ -119,6 +119,10 @@
|
||||
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
background: var(--l3-background);
|
||||
|
||||
&.version-container-standalone {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.version {
|
||||
|
||||
@@ -1010,7 +1010,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
<img src={signozBrandLogoUrl} alt="SigNoz" />
|
||||
</div>
|
||||
|
||||
{licenseTag && (
|
||||
{(licenseTag || currentVersion) && (
|
||||
<div
|
||||
className={cx(
|
||||
'brand-title-section',
|
||||
@@ -1021,7 +1021,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
'version-update-notification',
|
||||
)}
|
||||
>
|
||||
<span className="license-type"> {licenseTag} </span>
|
||||
{licenseTag && <span className="license-type"> {licenseTag} </span>}
|
||||
|
||||
{currentVersion && (
|
||||
<Tooltip
|
||||
@@ -1043,7 +1043,12 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="version-container">
|
||||
<div
|
||||
className={cx(
|
||||
'version-container',
|
||||
!licenseTag && 'version-container-standalone',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cx('version', changelog && 'version-clickable')}
|
||||
onClick={onClickVersionHandler}
|
||||
|
||||
5
frontend/src/lib/recentQueries/constants.ts
Normal file
5
frontend/src/lib/recentQueries/constants.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const STORAGE_KEY_PREFIX = 'qb_recent_v1';
|
||||
export const STORAGE_VERSION = 1;
|
||||
// Maximum entries kept per (signal, source) bucket. Larger than the UI's
|
||||
// RECENTS_DISPLAY_CAP so deleting a visible entry surfaces an older one.
|
||||
export const MAX_ENTRIES = 10;
|
||||
15
frontend/src/lib/recentQueries/getRecentQueries.ts
Normal file
15
frontend/src/lib/recentQueries/getRecentQueries.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { SignalType } from 'types/api/v5/queryRange';
|
||||
|
||||
import * as store from './recentQueriesStore';
|
||||
import type { RecentQueryEntry } from './types';
|
||||
|
||||
// Synchronous, non-subscribing read of the recent-queries bucket for a given
|
||||
// (signal, source). Read-on-demand by design — subscribing here would
|
||||
// reconfigure CodeMirror on every store change and close any open completion
|
||||
// popup. Pair with saveQuery() for the write path.
|
||||
export function getRecentQueries(
|
||||
signal: SignalType,
|
||||
source = '',
|
||||
): RecentQueryEntry[] {
|
||||
return store.list(signal, source);
|
||||
}
|
||||
100
frontend/src/lib/recentQueries/normalize.test.ts
Normal file
100
frontend/src/lib/recentQueries/normalize.test.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { normalizeFilterExpression } from './normalize';
|
||||
|
||||
describe('normalizeFilterExpression', () => {
|
||||
it('returns empty string for empty input', () => {
|
||||
expect(normalizeFilterExpression('')).toBe('');
|
||||
});
|
||||
|
||||
it('returns empty string for whitespace-only input', () => {
|
||||
expect(normalizeFilterExpression(' \t ')).toBe('');
|
||||
});
|
||||
|
||||
it('strips whitespace around operators', () => {
|
||||
expect(normalizeFilterExpression(' a = 1 ')).toBe('a=1');
|
||||
expect(normalizeFilterExpression('a = 1')).toBe('a=1');
|
||||
expect(normalizeFilterExpression('a=1')).toBe('a=1');
|
||||
});
|
||||
|
||||
it('lowercases AND / OR / NOT outside quotes', () => {
|
||||
expect(normalizeFilterExpression('A AND B OR NOT C')).toBe('AandBornotC');
|
||||
});
|
||||
|
||||
it('lowercases IN / LIKE / ILIKE / CONTAINS', () => {
|
||||
expect(normalizeFilterExpression('host IN [1, 2] AND name LIKE "foo"')).toBe(
|
||||
'hostin[1,2]andnamelike"foo"',
|
||||
);
|
||||
});
|
||||
|
||||
it('lowercases REGEXP', () => {
|
||||
expect(normalizeFilterExpression('path REGEXP "foo"')).toBe(
|
||||
'pathregexp"foo"',
|
||||
);
|
||||
expect(normalizeFilterExpression('path REGEXP "foo"')).toBe(
|
||||
normalizeFilterExpression('path regexp "foo"'),
|
||||
);
|
||||
});
|
||||
|
||||
it('lowercases HAS / HASANY / HASALL / HASTOKEN function names', () => {
|
||||
expect(normalizeFilterExpression('HAS(tags, "x")')).toBe(
|
||||
normalizeFilterExpression('has(tags, "x")'),
|
||||
);
|
||||
expect(normalizeFilterExpression('HASANY(tags, ["a","b"])')).toBe(
|
||||
normalizeFilterExpression('hasAny(tags, ["a","b"])'),
|
||||
);
|
||||
expect(normalizeFilterExpression('HASALL(tags, ["a","b"])')).toBe(
|
||||
normalizeFilterExpression('hasAll(tags, ["a","b"])'),
|
||||
);
|
||||
expect(normalizeFilterExpression('HASTOKEN(msg, "err")')).toBe(
|
||||
normalizeFilterExpression('hasToken(msg, "err")'),
|
||||
);
|
||||
});
|
||||
|
||||
it('lowercases TRUE / FALSE boolean literals', () => {
|
||||
expect(normalizeFilterExpression('active = TRUE')).toBe(
|
||||
normalizeFilterExpression('active = true'),
|
||||
);
|
||||
expect(normalizeFilterExpression('active = FALSE')).toBe(
|
||||
normalizeFilterExpression('active = false'),
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves whitespace and casing inside single-quoted strings', () => {
|
||||
expect(normalizeFilterExpression("a = 'X Y'")).toBe("a='X Y'");
|
||||
});
|
||||
|
||||
it('preserves whitespace and casing inside double-quoted strings', () => {
|
||||
expect(normalizeFilterExpression('a = "X Y"')).toBe('a="X Y"');
|
||||
});
|
||||
|
||||
it('does not lowercase keyword-looking substrings inside quotes', () => {
|
||||
expect(normalizeFilterExpression("msg = 'AND ERROR'")).toBe(
|
||||
"msg='AND ERROR'",
|
||||
);
|
||||
});
|
||||
|
||||
it('handles escaped quotes inside strings', () => {
|
||||
expect(normalizeFilterExpression("msg = 'a\\'b' AND x = 1")).toBe(
|
||||
"msg='a\\'b'andx=1",
|
||||
);
|
||||
});
|
||||
|
||||
it('treats two formattings of the same expression as identical', () => {
|
||||
const a = normalizeFilterExpression(
|
||||
'service.name = "frontend" AND severity = error',
|
||||
);
|
||||
const b = normalizeFilterExpression(
|
||||
'service.name="frontend" and severity=error',
|
||||
);
|
||||
expect(a).toBe(b);
|
||||
});
|
||||
|
||||
it('preserves unquoted value casing (treats them as identifiers)', () => {
|
||||
expect(normalizeFilterExpression('status = OK')).toBe('status=OK');
|
||||
});
|
||||
|
||||
it('handles mixed quotes in one expression', () => {
|
||||
expect(normalizeFilterExpression(`a = 'X' AND b = "Y"`)).toBe(
|
||||
`a='X'andb="Y"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
56
frontend/src/lib/recentQueries/normalize.ts
Normal file
56
frontend/src/lib/recentQueries/normalize.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
OPERATORS,
|
||||
QUERY_BUILDER_FUNCTIONS,
|
||||
TRACE_OPERATOR_OPERATORS,
|
||||
} from 'constants/antlrQueryConstants';
|
||||
|
||||
// Reserved keywords sourced from the ANTLR grammar constants so this list stays
|
||||
// in sync with the parser. `\b` prevents partial matches inside identifiers
|
||||
// (e.g. `OR` inside `originator`). `TRUE`/`FALSE` are BOOL literals, included
|
||||
// so case variants of boolean values also dedup.
|
||||
const WORD_KEYWORDS = [
|
||||
...Object.keys(OPERATORS).filter((k) => /^[A-Z]+$/.test(k)),
|
||||
...Object.keys(TRACE_OPERATOR_OPERATORS).filter((k) => /^[A-Z]+$/.test(k)),
|
||||
...Object.values(QUERY_BUILDER_FUNCTIONS),
|
||||
'TRUE',
|
||||
'FALSE',
|
||||
];
|
||||
|
||||
const KEYWORDS_RE = new RegExp(`\\b(${WORD_KEYWORDS.join('|')})\\b`, 'gi');
|
||||
|
||||
// Matches single- or double-quoted string literals, supporting escaped quotes
|
||||
// (e.g. `'it\'s'` or `"a \" b"`). We preserve quoted spans verbatim during
|
||||
// normalisation so user-meaningful whitespace and casing inside string values
|
||||
// stays intact: `name = "Foo Bar"` must not collapse to `name="foobar"`.
|
||||
const QUOTED_RE = /'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"/g;
|
||||
|
||||
// Lowercases reserved keywords and strips ALL whitespace from the unquoted regions
|
||||
// of the input. Keywords are normalised so casing variants dedup; whitespace is
|
||||
// dropped so formatting variants (`a=1` vs `a = 1`) dedup too.
|
||||
function processOutsideQuotes(s: string): string {
|
||||
return s.replace(KEYWORDS_RE, (m) => m.toLowerCase()).replace(/\s+/g, '');
|
||||
}
|
||||
|
||||
// Produces a canonical form of a filter expression suitable for dedup-key derivation.
|
||||
// Walks the input alternating between unquoted regions (where we normalise keywords
|
||||
// and whitespace) and quoted regions (which we copy verbatim).
|
||||
export function normalizeFilterExpression(input: string): string {
|
||||
if (!input) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let result = '';
|
||||
let lastIndex = 0;
|
||||
QUOTED_RE.lastIndex = 0;
|
||||
|
||||
let match = QUOTED_RE.exec(input);
|
||||
while (match !== null) {
|
||||
result += processOutsideQuotes(input.slice(lastIndex, match.index));
|
||||
result += match[0];
|
||||
lastIndex = QUOTED_RE.lastIndex;
|
||||
match = QUOTED_RE.exec(input);
|
||||
}
|
||||
result += processOutsideQuotes(input.slice(lastIndex));
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
247
frontend/src/lib/recentQueries/recentQueriesStore.test.ts
Normal file
247
frontend/src/lib/recentQueries/recentQueriesStore.test.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { MAX_ENTRIES } from './constants';
|
||||
import * as store from './recentQueriesStore';
|
||||
import type { RecentQueryInput } from './recentQueriesStore';
|
||||
import type { RecentQueryEntry } from './types';
|
||||
|
||||
const baseInput = (
|
||||
overrides: Partial<RecentQueryInput> = {},
|
||||
): RecentQueryInput => ({
|
||||
signal: 'logs',
|
||||
filter: { expression: "service.name = 'frontend'" },
|
||||
...overrides,
|
||||
});
|
||||
|
||||
function saveOrThrow(input: RecentQueryInput): RecentQueryEntry {
|
||||
const saved = store.save(input);
|
||||
if (!saved) {
|
||||
throw new Error('expected save to return an entry');
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
describe('recentQueries store', () => {
|
||||
beforeEach(() => {
|
||||
store.useRecentQueriesStore.setState({ buckets: {} });
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('save + list', () => {
|
||||
it('saves an entry and lists it', () => {
|
||||
store.save(baseInput());
|
||||
const entries = store.list('logs');
|
||||
expect(entries).toHaveLength(1);
|
||||
expect(entries[0].filter.expression).toBe("service.name = 'frontend'");
|
||||
expect(entries[0].id).toBeTruthy();
|
||||
expect(entries[0].lastUsedAt).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('does not save when the filter expression is empty', () => {
|
||||
const result = store.save(baseInput({ filter: { expression: '' } }));
|
||||
expect(result).toBeNull();
|
||||
expect(store.list('logs')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not save when the filter expression is whitespace only', () => {
|
||||
const result = store.save(baseInput({ filter: { expression: ' ' } }));
|
||||
expect(result).toBeNull();
|
||||
expect(store.list('logs')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('LRU ordering', () => {
|
||||
it('places the most recently saved entry at the front', () => {
|
||||
store.save(baseInput({ filter: { expression: "severity_text = 'ERROR'" } }));
|
||||
store.save(baseInput({ filter: { expression: 'http.status_code >= 500' } }));
|
||||
store.save(baseInput({ filter: { expression: 'attempt = 1' } }));
|
||||
|
||||
const entries = store.list('logs');
|
||||
expect(entries.map((e) => e.filter.expression)).toStrictEqual([
|
||||
'attempt = 1',
|
||||
'http.status_code >= 500',
|
||||
"severity_text = 'ERROR'",
|
||||
]);
|
||||
});
|
||||
|
||||
it('re-saving an existing filter bumps it to the front', () => {
|
||||
store.save(baseInput({ filter: { expression: "severity_text = 'ERROR'" } }));
|
||||
store.save(baseInput({ filter: { expression: 'http.status_code >= 500' } }));
|
||||
store.save(baseInput({ filter: { expression: "severity_text = 'ERROR'" } }));
|
||||
|
||||
const entries = store.list('logs');
|
||||
expect(entries).toHaveLength(2);
|
||||
expect(entries.map((e) => e.filter.expression)).toStrictEqual([
|
||||
"severity_text = 'ERROR'",
|
||||
'http.status_code >= 500',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dedup', () => {
|
||||
it('treats formatting variations of the same filter as one entry', () => {
|
||||
store.save(
|
||||
baseInput({
|
||||
filter: { expression: "severity_text = 'ERROR' AND attempt = 1" },
|
||||
}),
|
||||
);
|
||||
store.save(
|
||||
baseInput({
|
||||
filter: { expression: "severity_text='ERROR' and attempt=1" },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(store.list('logs')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('signal partitioning', () => {
|
||||
it('saves to the right bucket per signal', () => {
|
||||
store.save(
|
||||
baseInput({
|
||||
signal: 'logs',
|
||||
filter: { expression: "severity_text = 'ERROR'" },
|
||||
}),
|
||||
);
|
||||
store.save(
|
||||
baseInput({
|
||||
signal: 'traces',
|
||||
filter: { expression: "service.name = 'orders-api'" },
|
||||
}),
|
||||
);
|
||||
store.save(
|
||||
baseInput({
|
||||
signal: 'metrics',
|
||||
filter: { expression: 'cpu_usage > 80' },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(store.list('logs')).toHaveLength(1);
|
||||
expect(store.list('traces')).toHaveLength(1);
|
||||
expect(store.list('metrics')).toHaveLength(1);
|
||||
expect(store.list('logs')[0].filter.expression).toBe(
|
||||
"severity_text = 'ERROR'",
|
||||
);
|
||||
expect(store.list('traces')[0].filter.expression).toBe(
|
||||
"service.name = 'orders-api'",
|
||||
);
|
||||
expect(store.list('metrics')[0].filter.expression).toBe('cpu_usage > 80');
|
||||
});
|
||||
|
||||
it('does not leak between signals on dedup', () => {
|
||||
store.save(
|
||||
baseInput({
|
||||
signal: 'logs',
|
||||
filter: { expression: "service.name = 'frontend'" },
|
||||
}),
|
||||
);
|
||||
store.save(
|
||||
baseInput({
|
||||
signal: 'traces',
|
||||
filter: { expression: "service.name = 'frontend'" },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(store.list('logs')).toHaveLength(1);
|
||||
expect(store.list('traces')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('LRU cap', () => {
|
||||
it('caps the bucket at MAX_ENTRIES and evicts the oldest', () => {
|
||||
const total = MAX_ENTRIES + 1;
|
||||
for (let i = 0; i < total; i += 1) {
|
||||
store.save(baseInput({ filter: { expression: `attempt = ${i}` } }));
|
||||
}
|
||||
|
||||
const entries = store.list('logs');
|
||||
expect(entries).toHaveLength(MAX_ENTRIES);
|
||||
expect(entries[0].filter.expression).toBe(`attempt = ${total - 1}`);
|
||||
expect(entries.some((e) => e.filter.expression === 'attempt = 0')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
it('removes an entry by id', () => {
|
||||
store.save(baseInput({ filter: { expression: "severity_text = 'ERROR'" } }));
|
||||
const saved = saveOrThrow(
|
||||
baseInput({ filter: { expression: 'http.status_code >= 500' } }),
|
||||
);
|
||||
store.remove(saved.id, 'logs');
|
||||
|
||||
const entries = store.list('logs');
|
||||
expect(entries).toHaveLength(1);
|
||||
expect(entries[0].filter.expression).toBe("severity_text = 'ERROR'");
|
||||
});
|
||||
|
||||
it('is a no-op when the id does not exist', () => {
|
||||
store.save(baseInput({ filter: { expression: "severity_text = 'ERROR'" } }));
|
||||
store.remove('does-not-exist', 'logs');
|
||||
expect(store.list('logs')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not touch other signals', () => {
|
||||
const logsEntry = saveOrThrow(
|
||||
baseInput({
|
||||
signal: 'logs',
|
||||
filter: { expression: "service.name = 'frontend'" },
|
||||
}),
|
||||
);
|
||||
store.save(
|
||||
baseInput({
|
||||
signal: 'traces',
|
||||
filter: { expression: "service.name = 'frontend'" },
|
||||
}),
|
||||
);
|
||||
|
||||
store.remove(logsEntry.id, 'logs');
|
||||
|
||||
expect(store.list('logs')).toHaveLength(0);
|
||||
expect(store.list('traces')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('persistence', () => {
|
||||
it('reads back the same entries after the in-memory state is reset', () => {
|
||||
store.save(baseInput({ filter: { expression: "severity_text = 'ERROR'" } }));
|
||||
store.save(baseInput({ filter: { expression: 'http.status_code >= 500' } }));
|
||||
|
||||
store.useRecentQueriesStore.setState({ buckets: {} });
|
||||
|
||||
const entries = store.list('logs');
|
||||
expect(entries.map((e) => e.filter.expression)).toStrictEqual([
|
||||
'http.status_code >= 500',
|
||||
"severity_text = 'ERROR'",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reactive subscription via zustand', () => {
|
||||
it('notifies zustand subscribers on save', () => {
|
||||
const cb = jest.fn();
|
||||
const unsubscribe = store.useRecentQueriesStore.subscribe(cb);
|
||||
store.save(baseInput({ filter: { expression: "severity_text = 'ERROR'" } }));
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it('notifies zustand subscribers on remove', () => {
|
||||
const saved = saveOrThrow(
|
||||
baseInput({ filter: { expression: "severity_text = 'ERROR'" } }),
|
||||
);
|
||||
const cb = jest.fn();
|
||||
const unsubscribe = store.useRecentQueriesStore.subscribe(cb);
|
||||
store.remove(saved.id, 'logs');
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it('stops notifying after unsubscribe', () => {
|
||||
const cb = jest.fn();
|
||||
const unsubscribe = store.useRecentQueriesStore.subscribe(cb);
|
||||
unsubscribe();
|
||||
store.save(baseInput({ filter: { expression: "severity_text = 'ERROR'" } }));
|
||||
expect(cb).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
144
frontend/src/lib/recentQueries/recentQueriesStore.ts
Normal file
144
frontend/src/lib/recentQueries/recentQueriesStore.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import get from 'api/browser/localstorage/get';
|
||||
import set from 'api/browser/localstorage/set';
|
||||
import type { SignalType } from 'types/api/v5/queryRange';
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { MAX_ENTRIES, STORAGE_VERSION } from './constants';
|
||||
import { normalizeFilterExpression } from './normalize';
|
||||
import type { RecentQueriesStoreShape, RecentQueryEntry } from './types';
|
||||
import { bucketKey, makeId, normalizeSource, storageKeyFor } from './utils';
|
||||
|
||||
// Mirrors parsed localStorage so equal raw strings return the same array ref —
|
||||
// preserves Object.is for zustand selector bail-out.
|
||||
const persistedBucketCache = new Map<
|
||||
string,
|
||||
{ raw: string; parsed: RecentQueryEntry[] }
|
||||
>();
|
||||
|
||||
function loadBucketFromStorage(
|
||||
signal: SignalType,
|
||||
source: string,
|
||||
): RecentQueryEntry[] | null {
|
||||
const key = bucketKey(signal, source);
|
||||
try {
|
||||
const raw = get(storageKeyFor(signal, source));
|
||||
if (!raw) {
|
||||
persistedBucketCache.delete(key);
|
||||
return null;
|
||||
}
|
||||
const cached = persistedBucketCache.get(key);
|
||||
if (cached && cached.raw === raw) {
|
||||
return cached.parsed;
|
||||
}
|
||||
const parsedShape = JSON.parse(raw) as RecentQueriesStoreShape;
|
||||
if (
|
||||
parsedShape?.version !== STORAGE_VERSION ||
|
||||
!Array.isArray(parsedShape.entries)
|
||||
) {
|
||||
persistedBucketCache.delete(key);
|
||||
return null;
|
||||
}
|
||||
persistedBucketCache.set(key, { raw, parsed: parsedShape.entries });
|
||||
return parsedShape.entries;
|
||||
} catch {
|
||||
persistedBucketCache.delete(key);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveBucketToStorage(
|
||||
signal: SignalType,
|
||||
source: string,
|
||||
entries: RecentQueryEntry[],
|
||||
): void {
|
||||
try {
|
||||
const raw = JSON.stringify({ version: STORAGE_VERSION, entries });
|
||||
if (set(storageKeyFor(signal, source), raw)) {
|
||||
persistedBucketCache.set(bucketKey(signal, source), {
|
||||
raw,
|
||||
parsed: entries,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Ignore storage errors (e.g. quota exceeded, JSON.stringify failure).
|
||||
}
|
||||
}
|
||||
|
||||
export type RecentQueryInput = Omit<
|
||||
RecentQueryEntry,
|
||||
'id' | 'lastUsedAt' | 'source'
|
||||
> & {
|
||||
source?: string;
|
||||
};
|
||||
|
||||
type RecentQueriesState = {
|
||||
buckets: Record<string, RecentQueryEntry[]>;
|
||||
save: (entry: RecentQueryInput) => RecentQueryEntry | null;
|
||||
remove: (id: string, signal: SignalType, source?: string) => void;
|
||||
};
|
||||
|
||||
export const useRecentQueriesStore = create<RecentQueriesState>()(
|
||||
(set, get) => ({
|
||||
buckets: {},
|
||||
|
||||
save: (entry): RecentQueryEntry | null => {
|
||||
const normalized = normalizeFilterExpression(entry.filter.expression);
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
const source = normalizeSource(entry.source);
|
||||
const key = bucketKey(entry.signal, source);
|
||||
|
||||
const current =
|
||||
get().buckets[key] ?? loadBucketFromStorage(entry.signal, source) ?? [];
|
||||
const filtered = current.filter(
|
||||
(e) => normalizeFilterExpression(e.filter.expression) !== normalized,
|
||||
);
|
||||
|
||||
const newEntry: RecentQueryEntry = {
|
||||
...entry,
|
||||
source,
|
||||
id: makeId(entry.signal, source, normalized),
|
||||
lastUsedAt: Date.now(),
|
||||
};
|
||||
|
||||
const next = [newEntry, ...filtered].slice(0, MAX_ENTRIES);
|
||||
set({ buckets: { ...get().buckets, [key]: next } });
|
||||
saveBucketToStorage(entry.signal, source, next);
|
||||
return newEntry;
|
||||
},
|
||||
|
||||
remove: (id, signal, source = ''): void => {
|
||||
const key = bucketKey(signal, source);
|
||||
const current = get().buckets[key] ?? loadBucketFromStorage(signal, source);
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
const next = current.filter((e) => e.id !== id);
|
||||
if (next.length === current.length) {
|
||||
return;
|
||||
}
|
||||
set({ buckets: { ...get().buckets, [key]: next } });
|
||||
saveBucketToStorage(signal, source, next);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Plain-function wrappers for non-React callers — same pattern as useColumnStore.ts.
|
||||
export function save(entry: RecentQueryInput): RecentQueryEntry | null {
|
||||
return useRecentQueriesStore.getState().save(entry);
|
||||
}
|
||||
|
||||
export function remove(id: string, signal: SignalType, source = ''): void {
|
||||
useRecentQueriesStore.getState().remove(id, signal, source);
|
||||
}
|
||||
|
||||
// Synchronous bucket read with localStorage fallback for non-React callers.
|
||||
export function list(signal: SignalType, source = ''): RecentQueryEntry[] {
|
||||
const key = bucketKey(signal, source);
|
||||
const state = useRecentQueriesStore.getState();
|
||||
if (state.buckets[key]) {
|
||||
return state.buckets[key];
|
||||
}
|
||||
return loadBucketFromStorage(signal, source) ?? [];
|
||||
}
|
||||
113
frontend/src/lib/recentQueries/saveRecentQuery.test.ts
Normal file
113
frontend/src/lib/recentQueries/saveRecentQuery.test.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import type { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { validateQuery } from 'utils/queryValidationUtils';
|
||||
|
||||
import * as store from './recentQueriesStore';
|
||||
import { saveRecentQuery } from './saveRecentQuery';
|
||||
|
||||
jest.mock('utils/queryValidationUtils', () => ({
|
||||
validateQuery: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockedValidateQuery = validateQuery as jest.MockedFunction<
|
||||
typeof validateQuery
|
||||
>;
|
||||
|
||||
const buildComposite = (
|
||||
overrides: Partial<IBuilderQuery>[] = [{}],
|
||||
): { builder: { queryData: IBuilderQuery[] } } => ({
|
||||
builder: {
|
||||
queryData: overrides.map((o, i) => ({
|
||||
queryName: `Q${i}`,
|
||||
dataSource: DataSource.LOGS,
|
||||
aggregateOperator: 'count',
|
||||
aggregateAttribute: undefined as never,
|
||||
functions: [],
|
||||
filter: { expression: 'service.name = "frontend"' },
|
||||
groupBy: [],
|
||||
expression: `Q${i}`,
|
||||
disabled: false,
|
||||
having: [],
|
||||
limit: null,
|
||||
stepInterval: null,
|
||||
orderBy: [],
|
||||
legend: '',
|
||||
...o,
|
||||
})) as IBuilderQuery[],
|
||||
},
|
||||
});
|
||||
|
||||
describe('saveRecentQuery', () => {
|
||||
beforeEach(() => {
|
||||
store.useRecentQueriesStore.setState({ buckets: {} });
|
||||
localStorage.clear();
|
||||
mockedValidateQuery.mockReturnValue({
|
||||
isValid: true,
|
||||
message: '',
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('saves the composite query when validation passes', () => {
|
||||
saveRecentQuery(buildComposite());
|
||||
|
||||
const entries = store.list('logs');
|
||||
expect(entries).toHaveLength(1);
|
||||
expect(entries[0].filter.expression).toBe('service.name = "frontend"');
|
||||
});
|
||||
|
||||
it('does not save when validateQuery rejects the expression', () => {
|
||||
mockedValidateQuery.mockReturnValue({
|
||||
isValid: false,
|
||||
message: 'bad',
|
||||
errors: [],
|
||||
});
|
||||
|
||||
saveRecentQuery(buildComposite());
|
||||
|
||||
expect(store.list('logs')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not save a builder query with an empty filter expression', () => {
|
||||
saveRecentQuery(buildComposite([{ filter: { expression: '' } }]));
|
||||
|
||||
expect(store.list('logs')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('saves each builder query in the composite separately', () => {
|
||||
saveRecentQuery(
|
||||
buildComposite([
|
||||
{
|
||||
dataSource: DataSource.LOGS,
|
||||
filter: { expression: "service.name = 'frontend'" },
|
||||
},
|
||||
{
|
||||
dataSource: DataSource.TRACES,
|
||||
filter: { expression: "service.name = 'orders-api'" },
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
expect(store.list('logs')).toHaveLength(1);
|
||||
expect(store.list('traces')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('skips builder queries whose dataSource is not a supported signal', () => {
|
||||
saveRecentQuery(
|
||||
buildComposite([{ dataSource: 'unknown' as IBuilderQuery['dataSource'] }]),
|
||||
);
|
||||
|
||||
expect(store.list('logs')).toHaveLength(0);
|
||||
expect(store.list('traces')).toHaveLength(0);
|
||||
expect(store.list('metrics')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('is a no-op when the composite is null, undefined, or empty', () => {
|
||||
saveRecentQuery(null);
|
||||
saveRecentQuery(undefined);
|
||||
saveRecentQuery({ builder: { queryData: [] } });
|
||||
saveRecentQuery({});
|
||||
|
||||
expect(store.list('logs')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
52
frontend/src/lib/recentQueries/saveRecentQuery.ts
Normal file
52
frontend/src/lib/recentQueries/saveRecentQuery.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import type { SignalType } from 'types/api/v5/queryRange';
|
||||
import { validateQuery } from 'utils/queryValidationUtils';
|
||||
|
||||
import * as store from './recentQueriesStore';
|
||||
|
||||
function toSignal(dataSource: IBuilderQuery['dataSource']): SignalType | null {
|
||||
if (
|
||||
dataSource === 'logs' ||
|
||||
dataSource === 'traces' ||
|
||||
dataSource === 'metrics'
|
||||
) {
|
||||
return dataSource;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
type CompositeWithBuilder = {
|
||||
builder?: { queryData?: IBuilderQuery[] };
|
||||
};
|
||||
|
||||
// Persists each builder query in the composite as a recent entry. Call this
|
||||
// only from explicit user-driven Run triggers — reacting to stagedQuery or any
|
||||
// other derived state pollutes recents with navigation/refresh/go-to traffic.
|
||||
export function saveRecentQuery(
|
||||
query: CompositeWithBuilder | null | undefined,
|
||||
): void {
|
||||
const queryData = query?.builder?.queryData;
|
||||
if (!Array.isArray(queryData) || queryData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
queryData.forEach((q) => {
|
||||
const expression = q.filter?.expression?.trim();
|
||||
if (!expression) {
|
||||
return;
|
||||
}
|
||||
const validation = validateQuery(expression);
|
||||
if (!validation.isValid) {
|
||||
return;
|
||||
}
|
||||
const signal = toSignal(q.dataSource);
|
||||
if (!signal) {
|
||||
return;
|
||||
}
|
||||
store.save({
|
||||
signal,
|
||||
source: q.source ?? '',
|
||||
filter: q.filter ?? { expression: '' },
|
||||
});
|
||||
});
|
||||
}
|
||||
14
frontend/src/lib/recentQueries/types.ts
Normal file
14
frontend/src/lib/recentQueries/types.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { Filter, SignalType } from 'types/api/v5/queryRange';
|
||||
|
||||
export interface RecentQueryEntry {
|
||||
id: string;
|
||||
signal: SignalType;
|
||||
source: string;
|
||||
filter: Filter;
|
||||
lastUsedAt: number;
|
||||
}
|
||||
|
||||
export interface RecentQueriesStoreShape {
|
||||
version: 1;
|
||||
entries: RecentQueryEntry[];
|
||||
}
|
||||
24
frontend/src/lib/recentQueries/utils.ts
Normal file
24
frontend/src/lib/recentQueries/utils.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { SignalType } from 'types/api/v5/queryRange';
|
||||
|
||||
import { STORAGE_KEY_PREFIX } from './constants';
|
||||
|
||||
export function normalizeSource(source: string | undefined): string {
|
||||
return source ?? '';
|
||||
}
|
||||
|
||||
export function bucketKey(signal: SignalType, source: string): string {
|
||||
return `${signal}:${source}`;
|
||||
}
|
||||
|
||||
export function storageKeyFor(signal: SignalType, source: string): string {
|
||||
return `${STORAGE_KEY_PREFIX}:${bucketKey(signal, source)}`;
|
||||
}
|
||||
|
||||
// Same (signal, source, normalized filter) ⇒ same id ⇒ upsert.
|
||||
export function makeId(
|
||||
signal: SignalType,
|
||||
source: string,
|
||||
normalizedFilter: string,
|
||||
): string {
|
||||
return `${signal}|${source}|${normalizedFilter}`;
|
||||
}
|
||||
53
frontend/src/pages/DashboardPage/DashboardPage.tsx
Normal file
53
frontend/src/pages/DashboardPage/DashboardPage.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Modal } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { AxiosError } from 'axios';
|
||||
import NotFound from 'components/NotFound';
|
||||
import Spinner from 'components/Spinner';
|
||||
import DashboardContainer from 'container/DashboardContainer';
|
||||
import { useDashboardBootstrap } from 'hooks/dashboard/useDashboardBootstrap';
|
||||
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
|
||||
import { ErrorType } from 'types/common';
|
||||
|
||||
function DashboardPage(): JSX.Element {
|
||||
const { dashboardId } = useParams<{ dashboardId: string }>();
|
||||
|
||||
const [onModal, Content] = Modal.useModal();
|
||||
|
||||
const { isLoading, isError, isFetching, error } = useDashboardBootstrap(
|
||||
dashboardId,
|
||||
{ confirm: onModal.confirm },
|
||||
);
|
||||
|
||||
const dashboardTitle = useDashboardStore((s) => s.dashboardData?.data.title);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = dashboardTitle || document.title;
|
||||
}, [dashboardTitle]);
|
||||
|
||||
const errorMessage = isError
|
||||
? (error as AxiosError<{ errorType: string }>)?.response?.data?.errorType
|
||||
: 'Something went wrong';
|
||||
|
||||
if (isError && !isFetching && errorMessage === ErrorType.NotFound) {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
if (isError && errorMessage) {
|
||||
return <Typography>{errorMessage}</Typography>;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner tip="Loading.." />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{Content}
|
||||
<DashboardContainer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardPage;
|
||||
@@ -1,53 +1,15 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Modal } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { AxiosError } from 'axios';
|
||||
import NotFound from 'components/NotFound';
|
||||
import Spinner from 'components/Spinner';
|
||||
import DashboardContainer from 'container/DashboardContainer';
|
||||
import { useDashboardBootstrap } from 'hooks/dashboard/useDashboardBootstrap';
|
||||
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
|
||||
import { ErrorType } from 'types/common';
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import DashboardPageV2 from 'pages/DashboardPageV2';
|
||||
|
||||
function DashboardPage(): JSX.Element {
|
||||
const { dashboardId } = useParams<{ dashboardId: string }>();
|
||||
import DashboardPage from './DashboardPage';
|
||||
|
||||
const [onModal, Content] = Modal.useModal();
|
||||
// Serves the V2 dashboard detail page when the `use_dashboard_v2` flag is active;
|
||||
// otherwise the existing V1 page. Lets V2 dark-ship behind the flag without
|
||||
// changing route definitions.
|
||||
function DashboardPageEntry(): JSX.Element {
|
||||
const isDashboardV2 = useIsDashboardV2();
|
||||
|
||||
const { isLoading, isError, isFetching, error } = useDashboardBootstrap(
|
||||
dashboardId,
|
||||
{ confirm: onModal.confirm },
|
||||
);
|
||||
|
||||
const dashboardTitle = useDashboardStore((s) => s.dashboardData?.data.title);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = dashboardTitle || document.title;
|
||||
}, [dashboardTitle]);
|
||||
|
||||
const errorMessage = isError
|
||||
? (error as AxiosError<{ errorType: string }>)?.response?.data?.errorType
|
||||
: 'Something went wrong';
|
||||
|
||||
if (isError && !isFetching && errorMessage === ErrorType.NotFound) {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
if (isError && errorMessage) {
|
||||
return <Typography>{errorMessage}</Typography>;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner tip="Loading.." />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{Content}
|
||||
<DashboardContainer />
|
||||
</>
|
||||
);
|
||||
return isDashboardV2 ? <DashboardPageV2 /> : <DashboardPage />;
|
||||
}
|
||||
|
||||
export default DashboardPage;
|
||||
export default DashboardPageEntry;
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
.dashboardDescriptionContainer {
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
background: unset;
|
||||
color: var(--l2-foreground);
|
||||
|
||||
:global(.ant-card-body) {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.dashboardDetails {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 16px 16px 0px 16px;
|
||||
align-items: flex-start;
|
||||
|
||||
.leftSection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 45%;
|
||||
height: 40px;
|
||||
|
||||
.dashboardImg {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.dashboardTitle {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px; /* 150% */
|
||||
letter-spacing: -0.08px;
|
||||
max-width: 80%;
|
||||
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.clickableTitle {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.titleEdit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.titleInput {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.titleEditActionButton {
|
||||
--button-height: auto;
|
||||
--button-padding: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.titleSaveActionButton {
|
||||
--button-border-color: var(--text-forest-700);
|
||||
--button-outlined-foreground: var(--text-forest-700);
|
||||
}
|
||||
|
||||
.publicDashboardIcon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.rightSection {
|
||||
display: flex;
|
||||
width: 55%;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
height: 40px;
|
||||
|
||||
.icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 32px;
|
||||
height: 34px;
|
||||
padding: 6px;
|
||||
justify-content: center;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 10px; /* 83.333% */
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.icons:hover {
|
||||
background-color: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboardTags {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding: 16px 16px 0px 16px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.tag {
|
||||
display: flex;
|
||||
padding: 4px 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
|
||||
color: var(--bg-sienna-400);
|
||||
text-align: center;
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
margin-inline-end: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboardDescriptionSection {
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px; /* 157.143% */
|
||||
letter-spacing: -0.07px;
|
||||
padding: 20px 16px 0px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboardSettings {
|
||||
width: 191px;
|
||||
height: 302px;
|
||||
flex-shrink: 0;
|
||||
|
||||
:global(.ant-popover-inner) {
|
||||
padding: 0px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
color-mix(in srgb, var(--card) 80%, transparent) 0%,
|
||||
color-mix(in srgb, var(--card) 90%, transparent) 98.68%
|
||||
) !important;
|
||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
.menuContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: unset;
|
||||
padding: 8px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
letter-spacing: 0.14px;
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.section1,
|
||||
.section2 {
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
.deleteDashboard button {
|
||||
color: var(--bg-cherry-400) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.deleteModal :global(.ant-modal-confirm-body) {
|
||||
align-items: center;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
|
||||
import styles from '../DashboardDescription.module.scss';
|
||||
|
||||
interface DashboardMetaProps {
|
||||
tags: string[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
function DashboardMeta({ tags, description }: DashboardMetaProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{tags.length > 0 && (
|
||||
<div className={styles.dashboardTags}>
|
||||
{tags.map((tag) => (
|
||||
<Badge key={tag} className={styles.tag}>
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!isEmpty(description) && (
|
||||
<section className={styles.dashboardDescriptionSection}>
|
||||
{description}
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardMeta;
|
||||
@@ -1,116 +0,0 @@
|
||||
import { KeyboardEvent } from 'react';
|
||||
import { Check, Globe, LockKeyhole, X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
|
||||
import styles from '../DashboardDescription.module.scss';
|
||||
|
||||
interface DashboardTitleProps {
|
||||
title: string;
|
||||
image: string;
|
||||
isPublicDashboard: boolean;
|
||||
isDashboardLocked: boolean;
|
||||
isEditable: boolean;
|
||||
isEditing: boolean;
|
||||
draft: string;
|
||||
onDraftChange: (value: string) => void;
|
||||
onStartEdit: () => void;
|
||||
onCommit: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
function DashboardTitle({
|
||||
title,
|
||||
image,
|
||||
isPublicDashboard,
|
||||
isDashboardLocked,
|
||||
isEditable,
|
||||
isEditing,
|
||||
draft,
|
||||
onDraftChange,
|
||||
onStartEdit,
|
||||
onCommit,
|
||||
onCancel,
|
||||
}: DashboardTitleProps): JSX.Element {
|
||||
const canEdit = isEditable && !isDashboardLocked;
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent<HTMLInputElement>): void => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
onCommit();
|
||||
} else if (event.key === 'Escape') {
|
||||
onCancel();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.leftSection}>
|
||||
<img src={image} alt="dashboard-img" className={styles.dashboardImg} />
|
||||
{isEditing ? (
|
||||
<div className={styles.titleEdit}>
|
||||
<Input
|
||||
autoFocus
|
||||
value={draft}
|
||||
testId="dashboard-title-input"
|
||||
maxLength={120}
|
||||
className={styles.titleInput}
|
||||
onChange={(e): void => onDraftChange(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
size="icon"
|
||||
className={cx(styles.titleEditActionButton, styles.titleSaveActionButton)}
|
||||
aria-label="Save title"
|
||||
testId="dashboard-title-save"
|
||||
onClick={onCommit}
|
||||
>
|
||||
<Check size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
color="destructive"
|
||||
size="icon"
|
||||
className={styles.titleEditActionButton}
|
||||
aria-label="Cancel title edit"
|
||||
testId="dashboard-title-cancel"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<TooltipSimple title={title.length > 30 ? title : ''}>
|
||||
<Typography.Text
|
||||
className={cx(styles.dashboardTitle, {
|
||||
[styles.clickableTitle]: canEdit,
|
||||
})}
|
||||
data-testid="dashboard-title"
|
||||
onClick={canEdit ? onStartEdit : undefined}
|
||||
>
|
||||
{title}
|
||||
</Typography.Text>
|
||||
</TooltipSimple>
|
||||
)}
|
||||
|
||||
{isPublicDashboard && (
|
||||
<TooltipSimple title="This dashboard is publicly accessible">
|
||||
<Globe size={14} className={styles.publicDashboardIcon} />
|
||||
</TooltipSimple>
|
||||
)}
|
||||
|
||||
{isDashboardLocked && (
|
||||
<TooltipSimple title="This dashboard is locked">
|
||||
<LockKeyhole size={14} />
|
||||
</TooltipSimple>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardTitle;
|
||||
@@ -0,0 +1,11 @@
|
||||
.dashboardActionsContainer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dashboardActionsSecondary {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
@@ -28,43 +28,40 @@ import { USER_ROLES } from 'types/roles';
|
||||
import ConfirmDeleteDialog from '../../components/ConfirmDeleteDialog/ConfirmDeleteDialog';
|
||||
import DashboardSettings from '../../DashboardSettings';
|
||||
import SettingsDrawer from '../SettingsDrawer';
|
||||
import styles from '../DashboardDescription.module.scss';
|
||||
import styles from './DashboardActions.module.scss';
|
||||
import { useDashboardStore } from '../../store/useDashboardStore';
|
||||
|
||||
interface DashboardActionsProps {
|
||||
title: string;
|
||||
dashboard: DashboardtypesGettableDashboardV2DTO;
|
||||
handle: FullScreenHandle;
|
||||
isDashboardLocked: boolean;
|
||||
editDashboard: boolean;
|
||||
isAuthor: boolean;
|
||||
addPanelPermission: boolean;
|
||||
onAddPanel: () => void;
|
||||
onLockToggle: () => void;
|
||||
onOpenRename: () => void;
|
||||
}
|
||||
|
||||
function DashboardActions({
|
||||
title,
|
||||
dashboard,
|
||||
handle,
|
||||
isDashboardLocked,
|
||||
editDashboard,
|
||||
isAuthor,
|
||||
addPanelPermission,
|
||||
onAddPanel,
|
||||
onLockToggle,
|
||||
onOpenRename,
|
||||
}: DashboardActionsProps): JSX.Element {
|
||||
const canEdit = useDashboardStore((s) => s.isEditable);
|
||||
const { user } = useAppContext();
|
||||
const { t } = useTranslation(['dashboard', 'common']);
|
||||
|
||||
const id = dashboard.id ?? '';
|
||||
const title = dashboard.spec?.display?.name ?? '';
|
||||
|
||||
const [isSettingsDrawerOpen, setIsSettingsDrawerOpen] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [state, setCopy] = useCopyToClipboard();
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState<boolean>(false);
|
||||
const deleteDashboardMutation = useDeleteDashboard(id);
|
||||
const deleteDashboardMutation = useDeleteDashboard(dashboard.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.error) {
|
||||
@@ -103,7 +100,7 @@ function DashboardActions({
|
||||
|
||||
const menuItems = useMemo<MenuItem[]>(() => {
|
||||
const editGroup: MenuItem[] = [];
|
||||
if (!isDashboardLocked && editDashboard) {
|
||||
if (canEdit) {
|
||||
editGroup.push({
|
||||
key: 'rename',
|
||||
label: 'Rename',
|
||||
@@ -159,7 +156,6 @@ function DashboardActions({
|
||||
);
|
||||
}, [
|
||||
isDashboardLocked,
|
||||
editDashboard,
|
||||
isAuthor,
|
||||
user.role,
|
||||
dashboard.createdBy,
|
||||
@@ -169,58 +165,60 @@ function DashboardActions({
|
||||
exportJSON,
|
||||
setCopy,
|
||||
dashboardDataJSON,
|
||||
canEdit,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={styles.rightSection}>
|
||||
<div className={styles.dashboardActionsContainer}>
|
||||
<DateTimeSelectionV2 showAutoRefresh hideShareModal />
|
||||
<DropdownMenuSimple menu={{ items: menuItems }}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
size="icon"
|
||||
prefix={<Ellipsis size={14} />}
|
||||
className={styles.icons}
|
||||
testId="options"
|
||||
/>
|
||||
</DropdownMenuSimple>
|
||||
{!isDashboardLocked && editDashboard && (
|
||||
<>
|
||||
<div className={styles.dashboardActionsSecondary}>
|
||||
<DropdownMenuSimple menu={{ items: menuItems }}>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
prefix={<Configure size="md" />}
|
||||
testId="show-drawer"
|
||||
onClick={(): void => setIsSettingsDrawerOpen(true)}
|
||||
size="icon"
|
||||
prefix={<Ellipsis size="md" />}
|
||||
testId="options"
|
||||
/>
|
||||
</DropdownMenuSimple>
|
||||
{canEdit && (
|
||||
<>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
prefix={<Configure size="md" />}
|
||||
testId="show-drawer"
|
||||
onClick={(): void => setIsSettingsDrawerOpen(true)}
|
||||
size="md"
|
||||
>
|
||||
Configure
|
||||
</Button>
|
||||
<SettingsDrawer
|
||||
drawerTitle="Dashboard Configuration"
|
||||
isOpen={isSettingsDrawerOpen}
|
||||
onClose={(): void => setIsSettingsDrawerOpen(false)}
|
||||
>
|
||||
<DashboardSettings dashboard={dashboard} />
|
||||
</SettingsDrawer>
|
||||
</>
|
||||
)}
|
||||
{!isDashboardLocked && (
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
onClick={onAddPanel}
|
||||
prefix={<Plus size="md" />}
|
||||
testId="add-panel-header"
|
||||
size="md"
|
||||
>
|
||||
Configure
|
||||
New Panel
|
||||
</Button>
|
||||
<SettingsDrawer
|
||||
drawerTitle="Dashboard Configuration"
|
||||
isOpen={isSettingsDrawerOpen}
|
||||
onClose={(): void => setIsSettingsDrawerOpen(false)}
|
||||
>
|
||||
<DashboardSettings dashboard={dashboard} />
|
||||
</SettingsDrawer>
|
||||
</>
|
||||
)}
|
||||
{!isDashboardLocked && addPanelPermission && (
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
onClick={onAddPanel}
|
||||
prefix={<Plus size="md" />}
|
||||
testId="add-panel-header"
|
||||
size="md"
|
||||
>
|
||||
New Panel
|
||||
</Button>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
<ConfirmDeleteDialog
|
||||
open={isDeleteOpen}
|
||||
title={`Delete dashboard "${title}"?`}
|
||||
description="This action cannot be undone."
|
||||
title={`Delete dashboard"?`}
|
||||
description={`Are you sure you want to delete this dashboard - "${title}"? This action cannot be undone.`}
|
||||
isLoading={deleteDashboardMutation.isLoading}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onClose={(): void => setIsDeleteOpen(false)}
|
||||
@@ -0,0 +1,61 @@
|
||||
.dashboardInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 40%;
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboardTitleContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dashboardImage {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dashboardTitle {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: fit-content;
|
||||
color: var(--l1-foreground);
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dashboardTitleHover {
|
||||
cursor: text !important;
|
||||
}
|
||||
|
||||
.dashboardTitleEditor {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboardTitleInput {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboardTitleActionButton {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dashboardTags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import { KeyboardEvent } from 'react';
|
||||
import { Check, Globe, LockKeyhole, X } from '@signozhq/icons';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
|
||||
import styles from './DashboardInfo.module.scss';
|
||||
import { useDashboardStore } from '../../store/useDashboardStore';
|
||||
|
||||
interface DashboardInfoProps {
|
||||
title: string;
|
||||
image: string;
|
||||
tags: string[];
|
||||
description: string;
|
||||
isPublicDashboard: boolean;
|
||||
isDashboardLocked: boolean;
|
||||
isEditing: boolean;
|
||||
draft: string;
|
||||
onDraftChange: (value: string) => void;
|
||||
onStartEdit: () => void;
|
||||
onCommit: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
function DashboardInfo({
|
||||
title,
|
||||
image,
|
||||
tags,
|
||||
description,
|
||||
isPublicDashboard,
|
||||
isDashboardLocked,
|
||||
isEditing,
|
||||
draft,
|
||||
onDraftChange,
|
||||
onStartEdit,
|
||||
onCommit,
|
||||
onCancel,
|
||||
}: DashboardInfoProps): JSX.Element {
|
||||
const canEdit = useDashboardStore((s) => s.isEditable);
|
||||
|
||||
const hasTags = tags.length > 0;
|
||||
const hasDescription = !isEmpty(description);
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent<HTMLInputElement>): void => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
onCommit();
|
||||
} else if (event.key === 'Escape') {
|
||||
onCancel();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.dashboardInfo}>
|
||||
<div className={styles.dashboardTitleContainer}>
|
||||
<img src={image} alt={title} className={styles.dashboardImage} />
|
||||
{isEditing ? (
|
||||
<div className={styles.dashboardTitleEditor}>
|
||||
<Input
|
||||
autoFocus
|
||||
value={draft}
|
||||
testId="dashboard-title-input"
|
||||
maxLength={120}
|
||||
className={styles.dashboardTitleInput}
|
||||
onChange={(e): void => onDraftChange(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
size="icon"
|
||||
className={styles.dashboardTitleActionButton}
|
||||
aria-label="Save title"
|
||||
testId="dashboard-title-save"
|
||||
onClick={onCommit}
|
||||
>
|
||||
<Check size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="icon"
|
||||
className={styles.dashboardTitleActionButton}
|
||||
aria-label="Cancel title edit"
|
||||
testId="dashboard-title-cancel"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<TooltipSimple title={title}>
|
||||
<Typography.Text
|
||||
className={cx(styles.dashboardTitle, {
|
||||
[styles.dashboardTitleHover]: canEdit,
|
||||
})}
|
||||
data-testid="dashboard-title"
|
||||
onClick={canEdit ? onStartEdit : undefined}
|
||||
>
|
||||
{title}
|
||||
</Typography.Text>
|
||||
</TooltipSimple>
|
||||
)}
|
||||
|
||||
{isPublicDashboard && (
|
||||
<TooltipSimple title="This dashboard is publicly accessible">
|
||||
<Globe size={14} />
|
||||
</TooltipSimple>
|
||||
)}
|
||||
|
||||
{isDashboardLocked && (
|
||||
<TooltipSimple title="This dashboard is locked">
|
||||
<LockKeyhole size={14} />
|
||||
</TooltipSimple>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasTags && (
|
||||
<div className={styles.dashboardTags}>
|
||||
{tags.map((tag) => (
|
||||
<Badge key={tag} color="warning" variant="outline">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasDescription && (
|
||||
<Typography.Text color="muted">{description}</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardInfo;
|
||||
@@ -0,0 +1,20 @@
|
||||
.dashboardPageToolbarContainer {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
color: var(--l2-foreground);
|
||||
background-color: var(--l1-background);
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 2px 0px var(--l2-border);
|
||||
}
|
||||
|
||||
.dashboardPageToolbarSubContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dashboardInfoWithActions {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { FullScreenHandle } from 'react-full-screen';
|
||||
import { Card } from 'antd';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import {
|
||||
@@ -13,34 +12,32 @@ import type {
|
||||
DashboardtypesJSONPatchOperationDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { Base64Icons } from 'container/DashboardContainer/DashboardSettings/General/utils';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { usePanelTypeSelectionModalStore } from 'providers/Dashboard/helpers/panelTypeSelectionModalHelper';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import DashboardHeader from '../components/DashboardHeader/DashboardHeader';
|
||||
import DashboardActions from './DashboardActions/DashboardActions';
|
||||
import DashboardMeta from './DashboardMeta/DashboardMeta';
|
||||
import DashboardTitle from './DashboardTitle/DashboardTitle';
|
||||
import { useEditableTitle } from './DashboardTitle/useEditableTitle';
|
||||
import DashboardInfo from './DashboardInfo/DashboardInfo';
|
||||
import { useEditableTitle } from './DashboardInfo/useEditableTitle';
|
||||
|
||||
import styles from './DashboardDescription.module.scss';
|
||||
import styles from './DashboardPageToolbar.module.scss';
|
||||
// import VariablesBar from '../VariablesBar/VariablesBar';
|
||||
|
||||
interface DashboardDescriptionProps {
|
||||
interface DashboardPageToolbarProps {
|
||||
dashboard: DashboardtypesGettableDashboardV2DTO;
|
||||
handle: FullScreenHandle;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
function DashboardPageToolbar(props: DashboardPageToolbarProps): JSX.Element {
|
||||
const { dashboard, handle, refetch } = props;
|
||||
|
||||
const id = dashboard.id;
|
||||
const isDashboardLocked = !!dashboard.locked;
|
||||
|
||||
const title = dashboard.spec?.display?.name ?? '';
|
||||
const description = dashboard.spec?.display?.description ?? '';
|
||||
const title = dashboard.spec.display.name;
|
||||
const description = dashboard.spec.display.description ?? '';
|
||||
const image = dashboard.image || Base64Icons[0];
|
||||
const tags = useMemo(
|
||||
() =>
|
||||
@@ -51,7 +48,6 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
);
|
||||
|
||||
const { user } = useAppContext();
|
||||
const [editDashboard] = useComponentPermission(['edit_dashboard'], user.role);
|
||||
const { showErrorModal } = useErrorModal();
|
||||
const setIsPanelTypeSelectionModalOpen = usePanelTypeSelectionModalStore(
|
||||
(s) => s.setIsPanelTypeSelectionModalOpen,
|
||||
@@ -59,9 +55,6 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
|
||||
const isAuthor =
|
||||
!!user?.email && !!dashboard.createdBy && dashboard.createdBy === user.email;
|
||||
const addPanelPermission = !isDashboardLocked;
|
||||
// V2 public dashboard wiring lives separately; treat as not-public for chrome.
|
||||
const isPublicDashboard = false;
|
||||
|
||||
const handleLockDashboardToggle = useCallback(async (): Promise<void> => {
|
||||
if (!id) {
|
||||
@@ -110,7 +103,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
onSave: onNameSave,
|
||||
});
|
||||
|
||||
const onEmptyWidgetHandler = useCallback((): void => {
|
||||
const onAddPanel = useCallback((): void => {
|
||||
void logEvent('Dashboard Detail V2: Add new panel clicked', {
|
||||
dashboardId: id,
|
||||
});
|
||||
@@ -118,15 +111,15 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
}, [id, setIsPanelTypeSelectionModalOpen]);
|
||||
|
||||
return (
|
||||
<Card className={styles.dashboardDescriptionContainer}>
|
||||
<DashboardHeader title={title} image={image} />
|
||||
<section className={styles.dashboardDetails}>
|
||||
<DashboardTitle
|
||||
<section className={styles.dashboardPageToolbarContainer}>
|
||||
<div className={styles.dashboardInfoWithActions}>
|
||||
<DashboardInfo
|
||||
title={title}
|
||||
image={image}
|
||||
isPublicDashboard={isPublicDashboard}
|
||||
tags={tags}
|
||||
description={description}
|
||||
isPublicDashboard={false}
|
||||
isDashboardLocked={isDashboardLocked}
|
||||
isEditable={editDashboard}
|
||||
isEditing={isEditing}
|
||||
draft={draft}
|
||||
onDraftChange={setDraft}
|
||||
@@ -135,20 +128,21 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
onCancel={cancel}
|
||||
/>
|
||||
<DashboardActions
|
||||
title={title}
|
||||
dashboard={dashboard}
|
||||
handle={handle}
|
||||
isDashboardLocked={isDashboardLocked}
|
||||
editDashboard={editDashboard}
|
||||
isAuthor={isAuthor}
|
||||
addPanelPermission={addPanelPermission}
|
||||
onAddPanel={onEmptyWidgetHandler}
|
||||
onAddPanel={onAddPanel}
|
||||
onLockToggle={handleLockDashboardToggle}
|
||||
onOpenRename={startEdit}
|
||||
/>
|
||||
</section>
|
||||
<DashboardMeta tags={tags} description={description} />
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardDescription;
|
||||
{
|
||||
/* <VariablesBar dashboard={dashboard} /> */
|
||||
}
|
||||
export default DashboardPageToolbar;
|
||||
@@ -1,3 +1,8 @@
|
||||
.tabsContent {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
padding: 24px;
|
||||
}
|
||||
@@ -9,3 +14,10 @@
|
||||
line-height: 1;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
// shared "settings card" wrapper, used by the dashboard-info form and cross-panel sync
|
||||
.settingsCard {
|
||||
padding: 24px 16px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--l2-border);
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
// eslint-disable-next-line signoz/no-antd-components -- TODO: migrate Select/Input to @signozhq/ui
|
||||
import { Col, Input, Select, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import AddTags from 'container/DashboardContainer/DashboardSettings/General/AddBadges';
|
||||
|
||||
import { Base64Icons } from '../utils';
|
||||
import styles from '../GeneralSettings.module.scss';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
interface GeneralFormProps {
|
||||
title: string;
|
||||
description: string;
|
||||
image: string;
|
||||
tags: string[];
|
||||
onTitleChange: (value: string) => void;
|
||||
onDescriptionChange: (value: string) => void;
|
||||
onImageChange: (value: string) => void;
|
||||
onTagsChange: Dispatch<SetStateAction<string[]>>;
|
||||
}
|
||||
|
||||
function GeneralForm({
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
tags,
|
||||
onTitleChange,
|
||||
onDescriptionChange,
|
||||
onImageChange,
|
||||
onTagsChange,
|
||||
}: GeneralFormProps): JSX.Element {
|
||||
return (
|
||||
<Col className={styles.overviewSettings}>
|
||||
<Space direction="vertical" className={styles.formSpace}>
|
||||
<div>
|
||||
<Typography className={styles.dashboardName}>Dashboard Name</Typography>
|
||||
<section className={styles.nameIconInput}>
|
||||
<Select
|
||||
defaultActiveFirstOption
|
||||
data-testid="dashboard-image"
|
||||
suffixIcon={null}
|
||||
rootClassName={styles.dashboardImageInput}
|
||||
value={image}
|
||||
onChange={onImageChange}
|
||||
>
|
||||
{Base64Icons.map((icon) => (
|
||||
<Option value={icon} key={icon}>
|
||||
<img
|
||||
src={icon}
|
||||
alt="dashboard-icon"
|
||||
className={styles.listItemImage}
|
||||
/>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<Input
|
||||
data-testid="dashboard-name"
|
||||
className={styles.dashboardNameInput}
|
||||
value={title}
|
||||
onChange={(e): void => onTitleChange(e.target.value)}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography className={styles.dashboardName}>Description</Typography>
|
||||
<Input.TextArea
|
||||
data-testid="dashboard-desc"
|
||||
rows={6}
|
||||
value={description}
|
||||
className={styles.descriptionTextArea}
|
||||
onChange={(e): void => onDescriptionChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography className={styles.dashboardName}>Tags</Typography>
|
||||
<AddTags tags={tags} setTags={onTagsChange} />
|
||||
</div>
|
||||
</Space>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
export default GeneralForm;
|
||||
@@ -1,238 +0,0 @@
|
||||
.overviewContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.overviewSettings {
|
||||
padding: 16px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
.crossPanelSyncGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.formSpace {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 21px;
|
||||
}
|
||||
|
||||
.crossPanelSyncSectionTitle {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.crossPanelSyncSectionHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.crossPanelSyncInfoIcon {
|
||||
cursor: help;
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipTitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipDescription {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipDocLink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--primary-background);
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.crossPanelSyncRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
& + & {
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
.crossPanelSyncInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.crossPanelSyncTitle {
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.crossPanelSyncDescription {
|
||||
color: var(--l3-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.nameIconInput {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dashboardImageInput {
|
||||
:global(.ant-select-selector) {
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 6px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l3-background) !important;
|
||||
|
||||
:global(.ant-select-selection-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&:global(.ant-select-dropdown) {
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
:global(.ant-select-item) {
|
||||
padding: 0px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
:global(.ant-select-item-option-content) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.listItemImage {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.dashboardNameInput {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
}
|
||||
|
||||
.dashboardName {
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.descriptionTextArea {
|
||||
padding: 6px 6px 6px 8px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
}
|
||||
|
||||
.overviewSettingsFooter {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: -webkit-fill-available;
|
||||
padding: 12px 16px 12px 0px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
height: 32px;
|
||||
border-top: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
}
|
||||
|
||||
.unsaved {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.unsavedDot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50px;
|
||||
background: var(--primary-background);
|
||||
box-shadow: 0px 0px 6px 0px
|
||||
color-mix(in srgb, var(--primary-background) 40%, transparent);
|
||||
}
|
||||
|
||||
.unsavedChanges {
|
||||
color: var(--bg-robin-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.footerActionBtns {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.discardBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.saveBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0px !important;
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
.crossPanelSyncGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.crossPanelSyncSectionHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.crossPanelsSyncSectionTitle {
|
||||
color: var(--l1-foreground);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.crossPanelSyncInfoIcon {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipTitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipDescription {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.crossPanelSyncTooltipDocLink {
|
||||
color: var(--primary-background);
|
||||
font-size: 12px;
|
||||
margin-top: 16px;
|
||||
vertical-align: middle;
|
||||
|
||||
// typography override
|
||||
--typography-text-display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.crossPanelSyncRow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
& + & {
|
||||
padding-top: 16px;
|
||||
border-top: 1px dashed var(--l2-border);
|
||||
}
|
||||
}
|
||||
|
||||
.crossPanelSyncInfo {
|
||||
display: flex;
|
||||
flex: 1 1 80px;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.crossPanelSyncTitle {
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.crossPanelSyncDescription {
|
||||
color: var(--l3-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user