mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-09 16:10:40 +01:00
Compare commits
4 Commits
nv/dashboa
...
feat/expor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6298fd564 | ||
|
|
b37617bdb3 | ||
|
|
ebb60b99b5 | ||
|
|
9b9c461fbd |
6
.github/workflows/integrationci.yaml
vendored
6
.github/workflows/integrationci.yaml
vendored
@@ -48,11 +48,7 @@ jobs:
|
||||
- logspipelines
|
||||
- passwordauthn
|
||||
- preference
|
||||
- querierlogs
|
||||
- queriertraces
|
||||
- queriermetrics
|
||||
- querierscalar
|
||||
- queriercommon
|
||||
- querier
|
||||
- rawexportdata
|
||||
- role
|
||||
- rootuser
|
||||
|
||||
@@ -155,8 +155,8 @@ querier:
|
||||
cache_ttl: 168h
|
||||
# The interval for recent data that should not be cached.
|
||||
flux_interval: 5m
|
||||
# The maximum number of queries a single query range request runs at once.
|
||||
max_concurrent_queries: 8
|
||||
# The maximum number of concurrent queries for missing ranges.
|
||||
max_concurrent_queries: 4
|
||||
# When filtering logs by trace_id, clamp the query window to the trace time
|
||||
# range with padding to include slightly delayed log exports. Logs only; set
|
||||
# to 0 to disable.
|
||||
|
||||
@@ -515,13 +515,6 @@ components:
|
||||
url:
|
||||
type: string
|
||||
type: object
|
||||
AuthtypesDeprecatedPostableUserRole:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
AuthtypesGettableAuthDomain:
|
||||
properties:
|
||||
authNProviderInfo:
|
||||
@@ -667,20 +660,17 @@ components:
|
||||
type: string
|
||||
userRoles:
|
||||
items:
|
||||
$ref: '#/components/schemas/AuthtypesDeprecatedPostableUserRole'
|
||||
$ref: '#/components/schemas/AuthtypesPostableUserRole'
|
||||
type: array
|
||||
required:
|
||||
- email
|
||||
type: object
|
||||
AuthtypesPostableUserRole:
|
||||
properties:
|
||||
roleId:
|
||||
type: string
|
||||
userId:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- userId
|
||||
- roleId
|
||||
- id
|
||||
type: object
|
||||
AuthtypesRelation:
|
||||
enum:
|
||||
@@ -4189,7 +4179,6 @@ components:
|
||||
- namespaces
|
||||
- clusters
|
||||
- volumes
|
||||
- kube_containers
|
||||
type: string
|
||||
InframonitoringtypesChecks:
|
||||
properties:
|
||||
@@ -4262,8 +4251,6 @@ components:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNodeCountsByReadiness'
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
podCountsByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
|
||||
required:
|
||||
- clusterName
|
||||
- clusterCPU
|
||||
@@ -4272,7 +4259,6 @@ components:
|
||||
- clusterMemoryAllocatable
|
||||
- nodeCountsByReadiness
|
||||
- podCountsByPhase
|
||||
- podCountsByStatus
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesClusters:
|
||||
@@ -4295,158 +4281,6 @@ components:
|
||||
- total
|
||||
- endTimeBeforeRetention
|
||||
type: object
|
||||
InframonitoringtypesContainerCountsByReady:
|
||||
properties:
|
||||
notReady:
|
||||
type: integer
|
||||
ready:
|
||||
type: integer
|
||||
required:
|
||||
- ready
|
||||
- notReady
|
||||
type: object
|
||||
InframonitoringtypesContainerCountsByStatus:
|
||||
properties:
|
||||
completed:
|
||||
type: integer
|
||||
containerCannotRun:
|
||||
type: integer
|
||||
containerCreating:
|
||||
type: integer
|
||||
crashLoopBackOff:
|
||||
type: integer
|
||||
createContainerConfigError:
|
||||
type: integer
|
||||
errImagePull:
|
||||
type: integer
|
||||
error:
|
||||
type: integer
|
||||
imagePullBackOff:
|
||||
type: integer
|
||||
oomKilled:
|
||||
type: integer
|
||||
running:
|
||||
type: integer
|
||||
terminated:
|
||||
type: integer
|
||||
unknown:
|
||||
type: integer
|
||||
waiting:
|
||||
type: integer
|
||||
required:
|
||||
- running
|
||||
- waiting
|
||||
- terminated
|
||||
- crashLoopBackOff
|
||||
- imagePullBackOff
|
||||
- errImagePull
|
||||
- createContainerConfigError
|
||||
- containerCreating
|
||||
- oomKilled
|
||||
- completed
|
||||
- error
|
||||
- containerCannotRun
|
||||
- unknown
|
||||
type: object
|
||||
InframonitoringtypesContainerReady:
|
||||
enum:
|
||||
- ready
|
||||
- not_ready
|
||||
- no_data
|
||||
type: string
|
||||
InframonitoringtypesContainerRecord:
|
||||
properties:
|
||||
containerCountsByReady:
|
||||
$ref: '#/components/schemas/InframonitoringtypesContainerCountsByReady'
|
||||
containerCountsByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesContainerCountsByStatus'
|
||||
containerName:
|
||||
type: string
|
||||
cpu:
|
||||
format: double
|
||||
type: number
|
||||
cpuLimitUtilization:
|
||||
format: double
|
||||
type: number
|
||||
cpuRequestUtilization:
|
||||
format: double
|
||||
type: number
|
||||
memory:
|
||||
format: double
|
||||
type: number
|
||||
memoryLimitUtilization:
|
||||
format: double
|
||||
type: number
|
||||
memoryRequestUtilization:
|
||||
format: double
|
||||
type: number
|
||||
meta:
|
||||
additionalProperties:
|
||||
type: string
|
||||
nullable: true
|
||||
type: object
|
||||
podUID:
|
||||
type: string
|
||||
ready:
|
||||
$ref: '#/components/schemas/InframonitoringtypesContainerReady'
|
||||
restarts:
|
||||
format: int64
|
||||
type: integer
|
||||
status:
|
||||
$ref: '#/components/schemas/InframonitoringtypesContainerStatus'
|
||||
required:
|
||||
- podUID
|
||||
- containerName
|
||||
- status
|
||||
- containerCountsByStatus
|
||||
- ready
|
||||
- containerCountsByReady
|
||||
- restarts
|
||||
- cpu
|
||||
- cpuRequestUtilization
|
||||
- cpuLimitUtilization
|
||||
- memory
|
||||
- memoryRequestUtilization
|
||||
- memoryLimitUtilization
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesContainerStatus:
|
||||
enum:
|
||||
- running
|
||||
- waiting
|
||||
- terminated
|
||||
- crashloopbackoff
|
||||
- imagepullbackoff
|
||||
- errimagepull
|
||||
- createcontainerconfigerror
|
||||
- containercreating
|
||||
- oomkilled
|
||||
- completed
|
||||
- error
|
||||
- containercannotrun
|
||||
- unknown
|
||||
- no_data
|
||||
type: string
|
||||
InframonitoringtypesContainers:
|
||||
properties:
|
||||
endTimeBeforeRetention:
|
||||
type: boolean
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesContainerRecord'
|
||||
type: array
|
||||
total:
|
||||
type: integer
|
||||
type:
|
||||
$ref: '#/components/schemas/InframonitoringtypesResponseType'
|
||||
warning:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryWarnData'
|
||||
required:
|
||||
- type
|
||||
- records
|
||||
- total
|
||||
- endTimeBeforeRetention
|
||||
type: object
|
||||
InframonitoringtypesDaemonSetRecord:
|
||||
properties:
|
||||
currentNodes:
|
||||
@@ -4480,8 +4314,6 @@ components:
|
||||
type: object
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
podCountsByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
|
||||
required:
|
||||
- daemonSetName
|
||||
- daemonSetCPU
|
||||
@@ -4493,7 +4325,6 @@ components:
|
||||
- desiredNodes
|
||||
- currentNodes
|
||||
- podCountsByPhase
|
||||
- podCountsByStatus
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesDaemonSets:
|
||||
@@ -4549,8 +4380,6 @@ components:
|
||||
type: object
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
podCountsByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
|
||||
required:
|
||||
- deploymentName
|
||||
- deploymentCPU
|
||||
@@ -4562,7 +4391,6 @@ components:
|
||||
- desiredPods
|
||||
- availablePods
|
||||
- podCountsByPhase
|
||||
- podCountsByStatus
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesDeployments:
|
||||
@@ -4695,8 +4523,6 @@ components:
|
||||
type: object
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
podCountsByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
|
||||
successfulPods:
|
||||
type: integer
|
||||
required:
|
||||
@@ -4712,7 +4538,6 @@ components:
|
||||
- failedPods
|
||||
- successfulPods
|
||||
- podCountsByPhase
|
||||
- podCountsByStatus
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesJobs:
|
||||
@@ -4803,14 +4628,11 @@ components:
|
||||
type: string
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
podCountsByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
|
||||
required:
|
||||
- namespaceName
|
||||
- namespaceCPU
|
||||
- namespaceMemory
|
||||
- podCountsByPhase
|
||||
- podCountsByStatus
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesNamespaces:
|
||||
@@ -4876,14 +4698,11 @@ components:
|
||||
type: string
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
podCountsByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
|
||||
required:
|
||||
- nodeName
|
||||
- condition
|
||||
- nodeCountsByReadiness
|
||||
- podCountsByPhase
|
||||
- podCountsByStatus
|
||||
- nodeCPU
|
||||
- nodeCPUAllocatable
|
||||
- nodeMemory
|
||||
@@ -4929,64 +4748,6 @@ components:
|
||||
- failed
|
||||
- unknown
|
||||
type: object
|
||||
InframonitoringtypesPodCountsByStatus:
|
||||
properties:
|
||||
completed:
|
||||
type: integer
|
||||
containerCannotRun:
|
||||
type: integer
|
||||
containerCreating:
|
||||
type: integer
|
||||
crashLoopBackOff:
|
||||
type: integer
|
||||
createContainerConfigError:
|
||||
type: integer
|
||||
errImagePull:
|
||||
type: integer
|
||||
error:
|
||||
type: integer
|
||||
evicted:
|
||||
type: integer
|
||||
failed:
|
||||
type: integer
|
||||
imagePullBackOff:
|
||||
type: integer
|
||||
nodeAffinity:
|
||||
type: integer
|
||||
nodeLost:
|
||||
type: integer
|
||||
oomKilled:
|
||||
type: integer
|
||||
pending:
|
||||
type: integer
|
||||
running:
|
||||
type: integer
|
||||
shutdown:
|
||||
type: integer
|
||||
unexpectedAdmissionError:
|
||||
type: integer
|
||||
unknown:
|
||||
type: integer
|
||||
required:
|
||||
- pending
|
||||
- running
|
||||
- failed
|
||||
- unknown
|
||||
- crashLoopBackOff
|
||||
- imagePullBackOff
|
||||
- errImagePull
|
||||
- createContainerConfigError
|
||||
- containerCreating
|
||||
- oomKilled
|
||||
- completed
|
||||
- error
|
||||
- containerCannotRun
|
||||
- evicted
|
||||
- nodeAffinity
|
||||
- nodeLost
|
||||
- shutdown
|
||||
- unexpectedAdmissionError
|
||||
type: object
|
||||
InframonitoringtypesPodPhase:
|
||||
enum:
|
||||
- pending
|
||||
@@ -5017,8 +4778,6 @@ components:
|
||||
type: number
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
podCountsByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
|
||||
podMemory:
|
||||
format: double
|
||||
type: number
|
||||
@@ -5030,11 +4789,6 @@ components:
|
||||
type: number
|
||||
podPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodPhase'
|
||||
podRestarts:
|
||||
format: int64
|
||||
type: integer
|
||||
podStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodStatus'
|
||||
podUID:
|
||||
type: string
|
||||
required:
|
||||
@@ -5047,34 +4801,9 @@ components:
|
||||
- podMemoryLimit
|
||||
- podPhase
|
||||
- podCountsByPhase
|
||||
- podStatus
|
||||
- podCountsByStatus
|
||||
- podRestarts
|
||||
- podAge
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesPodStatus:
|
||||
enum:
|
||||
- pending
|
||||
- running
|
||||
- failed
|
||||
- unknown
|
||||
- crashloopbackoff
|
||||
- imagepullbackoff
|
||||
- errimagepull
|
||||
- createcontainerconfigerror
|
||||
- containercreating
|
||||
- oomkilled
|
||||
- completed
|
||||
- error
|
||||
- containercannotrun
|
||||
- evicted
|
||||
- nodeaffinity
|
||||
- nodelost
|
||||
- shutdown
|
||||
- unexpectedadmissionerror
|
||||
- no_data
|
||||
type: string
|
||||
InframonitoringtypesPods:
|
||||
properties:
|
||||
endTimeBeforeRetention:
|
||||
@@ -5121,32 +4850,6 @@ components:
|
||||
- end
|
||||
- limit
|
||||
type: object
|
||||
InframonitoringtypesPostableContainers:
|
||||
properties:
|
||||
end:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
nullable: true
|
||||
type: array
|
||||
limit:
|
||||
type: integer
|
||||
offset:
|
||||
type: integer
|
||||
orderBy:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5OrderBy'
|
||||
start:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- start
|
||||
- end
|
||||
- limit
|
||||
type: object
|
||||
InframonitoringtypesPostableDaemonSets:
|
||||
properties:
|
||||
end:
|
||||
@@ -5399,8 +5102,6 @@ components:
|
||||
type: object
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
podCountsByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
|
||||
statefulSetCPU:
|
||||
format: double
|
||||
type: number
|
||||
@@ -5432,7 +5133,6 @@ components:
|
||||
- desiredPods
|
||||
- currentPods
|
||||
- podCountsByPhase
|
||||
- podCountsByStatus
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesStatefulSets:
|
||||
@@ -7730,13 +7430,6 @@ components:
|
||||
enum:
|
||||
- basic
|
||||
type: string
|
||||
ServiceaccounttypesDeprecatedPostableServiceAccountRole:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
ServiceaccounttypesGettableFactorAPIKey:
|
||||
properties:
|
||||
createdAt:
|
||||
@@ -7793,13 +7486,10 @@ components:
|
||||
type: object
|
||||
ServiceaccounttypesPostableServiceAccountRole:
|
||||
properties:
|
||||
roleId:
|
||||
type: string
|
||||
serviceAccountId:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- serviceAccountId
|
||||
- roleId
|
||||
- id
|
||||
type: object
|
||||
ServiceaccounttypesServiceAccount:
|
||||
properties:
|
||||
@@ -12562,188 +12252,6 @@ paths:
|
||||
summary: Update route policy
|
||||
tags:
|
||||
- routepolicies
|
||||
/api/v1/service_account_roles:
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint assigns a role to a service account
|
||||
operationId: CreateServiceAccountRole
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ServiceaccounttypesPostableServiceAccountRole'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesIdentifiable'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: Created
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- serviceaccount:attach
|
||||
- role:attach
|
||||
- tokenizer:
|
||||
- serviceaccount:attach
|
||||
- role:attach
|
||||
summary: Create service account role
|
||||
tags:
|
||||
- serviceaccount
|
||||
/api/v1/service_account_roles/{id}:
|
||||
delete:
|
||||
deprecated: false
|
||||
description: This endpoint revokes a role from a service account
|
||||
operationId: DeleteServiceAccountRole
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- serviceaccount:detach
|
||||
- role:detach
|
||||
- tokenizer:
|
||||
- serviceaccount:detach
|
||||
- role:detach
|
||||
summary: Delete service account role
|
||||
tags:
|
||||
- serviceaccount
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint gets an existing service account role
|
||||
operationId: GetServiceAccountRole
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/ServiceaccounttypesServiceAccountRole'
|
||||
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:
|
||||
- serviceaccount:read
|
||||
- tokenizer:
|
||||
- serviceaccount:read
|
||||
summary: Get service account role
|
||||
tags:
|
||||
- serviceaccount
|
||||
/api/v1/service_accounts:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -13313,9 +12821,9 @@ paths:
|
||||
tags:
|
||||
- serviceaccount
|
||||
post:
|
||||
deprecated: true
|
||||
deprecated: false
|
||||
description: This endpoint assigns a role to a service account
|
||||
operationId: CreateServiceAccountRoleDeprecated
|
||||
operationId: CreateServiceAccountRole
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
@@ -13326,7 +12834,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ServiceaccounttypesDeprecatedPostableServiceAccountRole'
|
||||
$ref: '#/components/schemas/ServiceaccounttypesPostableServiceAccountRole'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
@@ -13378,9 +12886,9 @@ paths:
|
||||
- serviceaccount
|
||||
/api/v1/service_accounts/{id}/roles/{rid}:
|
||||
delete:
|
||||
deprecated: true
|
||||
deprecated: false
|
||||
description: This endpoint revokes a role from service account
|
||||
operationId: DeleteServiceAccountRoleDeprecated
|
||||
operationId: DeleteServiceAccountRole
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
@@ -16278,86 +15786,6 @@ paths:
|
||||
summary: List Jobs for Infra Monitoring
|
||||
tags:
|
||||
- inframonitoring
|
||||
/api/v2/infra_monitoring/kube_containers:
|
||||
post:
|
||||
deprecated: false
|
||||
description: 'Returns a paginated list of Kubernetes containers with key kubeletstats
|
||||
metrics: CPU usage (cores), CPU request/limit utilization, memory working
|
||||
set, and memory request/limit utilization. Each container also reports health
|
||||
signals from the k8s_cluster receiver: status (kubectl-style display status
|
||||
derived from k8s.container.status.state + k8s.container.status.reason), restarts
|
||||
(absolute count from k8s.container.restarts), and ready (ready/not_ready from
|
||||
k8s.container.ready). The row identity is (k8s.pod.uid, k8s.container.name),
|
||||
stable across container restarts. Each container includes metadata attributes
|
||||
(k8s.container.name, k8s.pod.name, container.image.name, container.image.tag,
|
||||
k8s.namespace.name, k8s.node.name, k8s.cluster.name, and workload owner such
|
||||
as deployment/statefulset/daemonset/job). The response type is ''list'' for
|
||||
the default (k8s.pod.uid, k8s.container.name) grouping (each row is one container
|
||||
with its current status and ready state) or ''grouped_list'' for custom groupBy
|
||||
keys (each row aggregates containers in the group with per-status counts under
|
||||
containerCountsByStatus, per-readiness counts under containerCountsByReady,
|
||||
and restarts as the group sum). Status requires the optional k8s.container.status.state
|
||||
and k8s.container.status.reason metrics; when either is missing, status is
|
||||
omitted and a warning is returned while restarts and ready are still computed.
|
||||
Supports filtering via a filter expression, custom groupBy, ordering by any
|
||||
of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit),
|
||||
and pagination via offset/limit. Also reports whether the requested time range
|
||||
falls before the data retention boundary. Numeric metric fields (cpu, cpuRequestUtilization,
|
||||
cpuLimitUtilization, memory, memoryRequestUtilization, memoryLimitUtilization)
|
||||
and restarts return -1 as a sentinel when no data is available for that field.'
|
||||
operationId: ListContainers
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPostableContainers'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/InframonitoringtypesContainers'
|
||||
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 Kubernetes Containers for Infra Monitoring
|
||||
tags:
|
||||
- inframonitoring
|
||||
/api/v2/infra_monitoring/namespaces:
|
||||
post:
|
||||
deprecated: false
|
||||
@@ -22562,184 +21990,6 @@ paths:
|
||||
summary: Rotate session
|
||||
tags:
|
||||
- sessions
|
||||
/api/v2/user_roles:
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint assigns a role to a user
|
||||
operationId: CreateUserRole
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthtypesPostableUserRole'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesIdentifiable'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: Created
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Create user role
|
||||
tags:
|
||||
- users
|
||||
/api/v2/user_roles/{id}:
|
||||
delete:
|
||||
deprecated: false
|
||||
description: This endpoint revokes a role from a user
|
||||
operationId: DeleteUserRole
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Delete user role
|
||||
tags:
|
||||
- users
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint gets an existing user role
|
||||
operationId: GetUserRole
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/AuthtypesUserRole'
|
||||
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:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Get user role
|
||||
tags:
|
||||
- users
|
||||
/api/v2/users:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -23193,7 +22443,7 @@ paths:
|
||||
tags:
|
||||
- users
|
||||
post:
|
||||
deprecated: true
|
||||
deprecated: false
|
||||
description: This endpoint assigns the role to the user roles by user id
|
||||
operationId: SetRoleByUserID
|
||||
parameters:
|
||||
@@ -23244,7 +22494,7 @@ paths:
|
||||
- users
|
||||
/api/v2/users/{id}/roles/{roleId}:
|
||||
delete:
|
||||
deprecated: true
|
||||
deprecated: false
|
||||
description: This endpoint removes a role from the user by user id and role
|
||||
id
|
||||
operationId: RemoveUserRoleByUserIDAndRoleID
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
# Authz
|
||||
|
||||
SigNoz uses [OpenFGA](https://openfga.dev/), a relationship-based access control (ReBAC) system, to authorize every request. Transactions are never attached to users directly — they are attached to **roles** (as relationship tuples in OpenFGA), and users or service accounts (principals) are made **assignees** of those roles. The central interface is `AuthZ` in [pkg/authz/authz.go](/pkg/authz/authz.go), backed by an embedded OpenFGA server in [pkg/authz/openfgaserver](/pkg/authz/openfgaserver/server.go).
|
||||
|
||||
As a feature author, you will rarely touch OpenFGA directly. You interact with two layers:
|
||||
|
||||
1. **The registries** in [pkg/types/coretypes](/pkg/types/coretypes) — where every resource, verb, and managed-role permission is declared in code.
|
||||
2. **The route wiring** in [pkg/apiserver/signozapiserver](/pkg/apiserver/signozapiserver) — where each route declares what resource it touches and which verb it needs, and the middleware turns that into a check.
|
||||
|
||||
## What are the building blocks?
|
||||
|
||||
### Type
|
||||
|
||||
A `Type` is the coarse FGA type of a resource. The set is finite and defined in [pkg/types/coretypes/registry_type.go](/pkg/types/coretypes/registry_type.go): `user`, `serviceaccount`, `anonymous`, `role`, `organization`, `metaresource`, and `telemetryresource`. Each type carries a selector regex (what a valid ID looks like) and the verbs allowed on it.
|
||||
|
||||
Almost every feature resource is a `metaresource` (dashboards, rules, pipelines, ...) or a `telemetryresource` (logs, traces, metrics). You should almost never need a new type.
|
||||
|
||||
### Verb
|
||||
|
||||
A `Verb` is the action being authorized. All verbs are defined in [pkg/types/coretypes/registry_verb.go](/pkg/types/coretypes/registry_verb.go): `create`, `read`, `update`, `delete`, `list`, `assignee`, `attach`, and `detach`.
|
||||
|
||||
- `assignee` is special: it is the membership relation between a subject and a role ("user X is an assignee of role Y"), not an action a route checks for.
|
||||
- `attach`/`detach` authorize linking two resources together (e.g. assigning a role to a service account).
|
||||
|
||||
### Kind
|
||||
|
||||
A `Kind` is the fine-grained name of your resource — `dashboard`, `rule`, `quick-filter`. Kinds are registered in [pkg/types/coretypes/registry_kind.go](/pkg/types/coretypes/registry_kind.go). A kind rides on top of an existing type, so adding one does **not** require any OpenFGA schema change.
|
||||
|
||||
### Resource
|
||||
|
||||
A `Resource` ties a type and a kind together and knows how to render itself as an FGA object string. The interface lives in [pkg/types/coretypes/resource.go](/pkg/types/coretypes/resource.go):
|
||||
|
||||
```go
|
||||
type Resource interface {
|
||||
Type() Type
|
||||
Kind() Kind
|
||||
Prefix(orgId valuer.UUID) string // metaresource:organization/<orgID>/dashboard
|
||||
Object(orgId valuer.UUID, selector string) string
|
||||
Scope(verb Verb) string // dashboard:read
|
||||
AllowedVerbs() []Verb
|
||||
}
|
||||
```
|
||||
|
||||
All resources are registered in [pkg/types/coretypes/registry_resource.go](/pkg/types/coretypes/registry_resource.go) using constructors like `NewResourceMetaResource(KindDashboard)` or `NewResourceTelemetryResource(KindLogs)`.
|
||||
|
||||
### Selector
|
||||
|
||||
A `Selector` identifies *which* instance(s) of a resource a check is about — a UUID, a role name, or the wildcard `*`. A `SelectorFunc` maps the extracted resource ID to selectors at request time. Two prebuilt ones cover most routes ([pkg/types/coretypes/selector.go](/pkg/types/coretypes/selector.go)):
|
||||
|
||||
- `WildcardSelector` — the check is against all instances of the resource (`create`, `list`).
|
||||
- `IDSelector` — the check is against the specific instance *or* the wildcard (`read`, `update`, `delete` of one object). A subject authorized on `*` is authorized on every instance.
|
||||
|
||||
When the ID in the request is not what FGA needs (e.g. routes receive a role UUID but FGA objects use role names), write a custom `SelectorFunc` — see `roleSelector` in [pkg/apiserver/signozapiserver/serviceaccount.go](/pkg/apiserver/signozapiserver/serviceaccount.go).
|
||||
|
||||
### Roles, transactions, and tuples
|
||||
|
||||
SigNoz ships four managed roles, declared in [pkg/types/coretypes/registry_managed_role.go](/pkg/types/coretypes/registry_managed_role.go): `signoz-admin`, `signoz-editor`, `signoz-viewer`, and `signoz-anonymous`. Their permissions are declared in code as `Transaction`s (a verb on an object) in `ManagedRoleToTransactions` — this map is the single source of truth for what each managed role can do.
|
||||
|
||||
At organization bootstrap, `CreateManagedRoles` and `CreateManagedUserRoleTransactions` (see [pkg/authz/authz.go](/pkg/authz/authz.go)) persist the role rows and write one OpenFGA tuple per transaction, linking `role:organization/<orgID>/role/<name>#assignee` to each permitted object. Users and service accounts are then granted roles via `Grant`/`Revoke`, which writes `assignee` tuples. Custom roles (enterprise) are managed through the roles API in [pkg/authz/signozauthzapi/handler.go](/pkg/authz/signozauthzapi/handler.go).
|
||||
|
||||
### Schema
|
||||
|
||||
The OpenFGA authorization model is a hand-written DSL, embedded at build time: [pkg/authz/openfgaschema/base.fga](/pkg/authz/openfgaschema/base.fga) for community and [ee/authz/openfgaschema/base.fga](/ee/authz/openfgaschema/base.fga) for enterprise. The community model only supports role assignment; the enterprise model defines per-verb relations on every type, enabling genuine per-resource checks. This split is why `CheckWithTupleCreation` behaves differently per edition (see [How does a check work at runtime?](#how-does-a-check-work-at-runtime)). You only touch these files when introducing a brand-new **type** — never for a new kind.
|
||||
|
||||
## How do I add authz to my feature?
|
||||
|
||||
### 1. Register the kind
|
||||
|
||||
Add your kind in [pkg/types/coretypes/registry_kind.go](/pkg/types/coretypes/registry_kind.go) and append it to `Kinds`:
|
||||
|
||||
```go
|
||||
KindThing = MustNewKind("thing")
|
||||
```
|
||||
|
||||
### 2. Register the resource
|
||||
|
||||
Add the resource in [pkg/types/coretypes/registry_resource.go](/pkg/types/coretypes/registry_resource.go) and append it to `Resources`:
|
||||
|
||||
```go
|
||||
ResourceMetaResourceThing = NewResourceMetaResource(KindThing)
|
||||
```
|
||||
|
||||
Pass an explicit verb list to `NewResourceMetaResource` only if your resource supports fewer verbs than the type default.
|
||||
|
||||
### 3. Grant permissions to managed roles
|
||||
|
||||
Decide what each managed role can do with your resource and add the transactions in [pkg/types/coretypes/registry_managed_role.go](/pkg/types/coretypes/registry_managed_role.go):
|
||||
|
||||
```go
|
||||
// thing — editors manage, viewers read
|
||||
{Verb: VerbCreate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindThing}, WildCardSelectorString)},
|
||||
```
|
||||
|
||||
The convention so far: admin gets everything, editor gets CRUD on day-to-day observability resources, viewer gets `read`/`list`, anonymous gets nothing (except public dashboards).
|
||||
|
||||
### 4. Wire the route
|
||||
|
||||
Register the route in [pkg/apiserver/signozapiserver](/pkg/apiserver/signozapiserver), wrapping your handler with `CheckResources` and declaring what the route touches via a `ResourceDef` ([pkg/http/handler/resourcedef.go](/pkg/http/handler/resourcedef.go)). A complete example from [pkg/apiserver/signozapiserver/serviceaccount.go](/pkg/apiserver/signozapiserver/serviceaccount.go):
|
||||
|
||||
```go
|
||||
router.Handle("/api/v1/service_accounts", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.Create, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateServiceAccount",
|
||||
// ...
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbCreate)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceServiceAccount,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: coretypes.ActionCategoryAccessControl,
|
||||
ID: coretypes.ResponseJSONPath("data.id"),
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodPost)
|
||||
```
|
||||
|
||||
The pieces:
|
||||
|
||||
- **`CheckResources(handlerFn, roles...)`** — the resource-aware authorization wrapper from [pkg/http/middleware/authz.go](/pkg/http/middleware/authz.go). The role list is the community-edition fallback: which managed roles may call this route when per-resource checks are unavailable.
|
||||
- **`ResourceDef`** — declares the resource, verb, audit category, how to extract the instance ID, and how to turn that ID into selectors. ID extractors live in [pkg/types/coretypes/extractor.go](/pkg/types/coretypes/extractor.go): `PathParam("id")`, `BodyJSONPath("data.id")`, `BodyJSONArray("ids")`, and `ResponseJSONPath("data.id")` for IDs only known after the handler runs (e.g. `create`).
|
||||
- **`SecuritySchemes`** — advertises the required scope (`resource.Scope(verb)`, e.g. `serviceaccount:create`) in the OpenAPI spec.
|
||||
|
||||
For routes that link two resources, use `AttachDetachSiblingResourceDef` (both sides are authz-checked, e.g. attaching a role to a service account requires `attach` on **both** the service account and the role). For parent-child routes (e.g. creating an API key under a service account), both sides are checked too, but with different verbs: declare a `BasicResourceDef` checking the child with `create`/`delete`, alongside an `AttachDetachParentChildResourceDef` checking the parent with `attach`/`detach` (within that def the child is only recorded for audit) — see the `/api/v1/service_accounts/{id}/keys` route in [pkg/apiserver/signozapiserver/serviceaccount.go](/pkg/apiserver/signozapiserver/serviceaccount.go).
|
||||
|
||||
Prefer `CheckResources` with a `ResourceDef` for anything resource-shaped. The older coarse gates `ViewAccess`/`EditAccess`/`AdminAccess` only check "does the caller hold one of these roles" and give up per-resource granularity; `OpenAccess` performs no authorization (authentication still applies); `CheckWithoutClaims` serves anonymous routes such as public dashboards.
|
||||
|
||||
### 5. Backfill existing organizations (only if needed)
|
||||
|
||||
Managed-role tuples are written from the registry at organization creation, so **new resources need no migration for new organizations**. If existing organizations must get the new permissions, add a migration in [pkg/sqlmigration](/pkg/sqlmigration) that inserts the tuples — see [083_add_role_crud_tuples.go](/pkg/sqlmigration/083_add_role_crud_tuples.go) for the pattern.
|
||||
|
||||
## How does a check work at runtime?
|
||||
|
||||
1. The resource middleware ([pkg/http/middleware/resource.go](/pkg/http/middleware/resource.go)) runs on every request. It reads the matched handler's `ResourceDef`s, extracts the resource IDs from path/body, and stores the resolved resources in the request context.
|
||||
2. `CheckResources` reads them back, runs each `SelectorFunc`, and calls `AuthZ.CheckWithTupleCreation(ctx, claims, orgID, relation, resource, selectors, roleSelectors)`.
|
||||
3. What happens next depends on the edition:
|
||||
- **Community** ([pkg/authz/openfgaserver/server.go](/pkg/authz/openfgaserver/server.go)) ignores the resource and selectors and only checks whether the subject is an `assignee` of one of the allowed roles — a plain role gate.
|
||||
- **Enterprise** ([ee/authz/openfgaserver/server.go](/ee/authz/openfgaserver/server.go)) builds tuples via `authtypes.NewTuples` — subject `user:organization/<orgID>/user/<userID>`, relation `create`, object `serviceaccount:organization/<orgID>/serviceaccount/*` — and batch-checks them against OpenFGA: genuine per-resource authorization, including custom roles.
|
||||
|
||||
Because both paths go through the same middleware and the same `ResourceDef` declarations, a route wired once works correctly in both editions.
|
||||
|
||||
## What should I remember?
|
||||
|
||||
- Declare authz in the registries ([pkg/types/coretypes](/pkg/types/coretypes)), not in migrations — tuples for new organizations are derived from code at bootstrap.
|
||||
- A new kind never needs an OpenFGA schema change; only a new type does, and then **both** [pkg/authz/openfgaschema/base.fga](/pkg/authz/openfgaschema/base.fga) and [ee/authz/openfgaschema/base.fga](/ee/authz/openfgaschema/base.fga) must be updated together.
|
||||
- Prefer `CheckResources` + `ResourceDef` over the coarse `ViewAccess`/`EditAccess`/`AdminAccess` gates for new routes.
|
||||
- Use `WildcardSelector` for `create`/`list`, `IDSelector` for instance operations, and a custom `SelectorFunc` when the request ID is not the FGA selector.
|
||||
- Linking routes check **both** sides: peers via `AttachDetachSiblingResourceDef` (same verb on both), parent-child via a `BasicResourceDef` on the child (`create`/`delete`) paired with an `AttachDetachParentChildResourceDef` on the parent (`attach`/`detach`).
|
||||
- Changing `ManagedRoleToTransactions` only affects organizations created afterwards — add a [pkg/sqlmigration](/pkg/sqlmigration) migration to backfill existing ones.
|
||||
@@ -11,7 +11,6 @@ We adhere to three primary style guides as our foundation:
|
||||
We **recommend** (almost enforce) reviewing these guides before contributing to the codebase. They provide valuable insights into writing idiomatic Go code and will help you understand our approach to backend development. In addition, we have a few additional rules that make certain areas stricter than the above which can be found in area-specific files in this package:
|
||||
|
||||
- [Abstractions](abstractions.md) - When to introduce new types and intermediate representations
|
||||
- [Authz](authz.md) - Authorization, roles, and access control
|
||||
- [Errors](errors.md) - Structured error handling
|
||||
- [Endpoint](endpoint.md) - HTTP endpoint patterns
|
||||
- [Flagger](flagger.md) - Feature flag patterns
|
||||
|
||||
@@ -519,7 +519,7 @@ func (module *module) getOrCreateAPIKey(ctx context.Context, orgID valuer.UUID,
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = module.serviceAccount.SetRoleByName(ctx, orgID, serviceAccount.ID, authtypes.SigNozViewerRoleName)
|
||||
err = module.serviceAccount.SetRoleByName(ctx, orgID, serviceAccount.ID, authtypes.SigNozViewerRoleName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationServiceAccount(ctx context.Cont
|
||||
if err != nil {
|
||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration service account: %w", err))
|
||||
}
|
||||
_, err = ah.Signoz.Modules.ServiceAccount.SetRoleByName(ctx, orgId, cloudIntegrationServiceAccount.ID, authtypes.SigNozViewerRoleName)
|
||||
err = ah.Signoz.Modules.ServiceAccount.SetRoleByName(ctx, orgId, cloudIntegrationServiceAccount.ID, authtypes.SigNozViewerRoleName)
|
||||
if err != nil {
|
||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration service account: %w", err))
|
||||
}
|
||||
|
||||
@@ -116,15 +116,6 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
infraMonitoringV2 := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureUseInfraMonitoringV2, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureUseInfraMonitoringV2.String()),
|
||||
Active: infraMonitoringV2,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
|
||||
@@ -25,7 +25,6 @@ You are operating within a constrained context window and strict system prompts.
|
||||
- Never create barrel files.
|
||||
- When writing new css, prefer CSS Modules
|
||||
- Use ./docs/css-modules-guide.md as reference on how to write good CSS Modules.
|
||||
- When writing code that could need authorization checks, read ./src/lib/authz/README.md
|
||||
|
||||
3. FORCED VERIFICATION: Your internal tools mark file writes as successful even if the code does not compile. You are FORBIDDEN from reporting a task as complete until you have:
|
||||
- Run `pnpm tsgo --noEmit`
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const IS_DEV = false;
|
||||
export const IS_PROD = true;
|
||||
export const MODE = 'test';
|
||||
@@ -29,7 +29,6 @@ const config: Config.InitialOptions = {
|
||||
'^constants/env$': '<rootDir>/__mocks__/env.ts',
|
||||
'^src/constants/env$': '<rootDir>/__mocks__/env.ts',
|
||||
'^@signozhq/icons$': '<rootDir>/__mocks__/signozhqIconsMock.tsx',
|
||||
'^lib/env$': '<rootDir>/__mocks__/lib/env.ts',
|
||||
'^test-mocks/(.*)$': '<rootDir>/__mocks__/$1',
|
||||
'^react-syntax-highlighter/dist/esm/(.*)$':
|
||||
'<rootDir>/node_modules/react-syntax-highlighter/dist/cjs/$1',
|
||||
|
||||
@@ -79,7 +79,6 @@
|
||||
"event-source-polyfill": "1.0.31",
|
||||
"eventemitter3": "5.0.1",
|
||||
"history": "4.10.1",
|
||||
"html-to-image": "1.11.13",
|
||||
"http-status-codes": "2.3.0",
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
|
||||
8
frontend/pnpm-lock.yaml
generated
8
frontend/pnpm-lock.yaml
generated
@@ -164,9 +164,6 @@ importers:
|
||||
history:
|
||||
specifier: 4.10.1
|
||||
version: 4.10.1
|
||||
html-to-image:
|
||||
specifier: 1.11.13
|
||||
version: 1.11.13
|
||||
http-status-codes:
|
||||
specifier: 2.3.0
|
||||
version: 2.3.0
|
||||
@@ -5454,9 +5451,6 @@ packages:
|
||||
resolution: {integrity: sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==}
|
||||
engines: {node: '>=20.10'}
|
||||
|
||||
html-to-image@1.11.13:
|
||||
resolution: {integrity: sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==}
|
||||
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||
|
||||
@@ -14501,8 +14495,6 @@ snapshots:
|
||||
|
||||
html-tags@5.1.0: {}
|
||||
|
||||
html-to-image@1.11.13: {}
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
|
||||
http-proxy-agent@5.0.0:
|
||||
|
||||
@@ -62,6 +62,6 @@
|
||||
"TRACES_FUNNELS_DETAIL": "SigNoz | Funnel",
|
||||
"INTEGRATIONS_DETAIL": "SigNoz | Integration",
|
||||
"PUBLIC_DASHBOARD": "SigNoz | Dashboard",
|
||||
"LLM_OBSERVABILITY_OVERVIEW": "SigNoz | LLM Observability Overview",
|
||||
"LLM_OBSERVABILITY_CONFIGURATION": "SigNoz | LLM Observability Configuration"
|
||||
}
|
||||
"LLM_OBSERVABILITY_BASE": "SigNoz | LLM Observability",
|
||||
"LLM_OBSERVABILITY_MODEL_PRICING": "SigNoz | Model Pricing"
|
||||
}
|
||||
|
||||
@@ -87,6 +87,6 @@
|
||||
"TRACES_FUNNELS_DETAIL": "SigNoz | Funnel",
|
||||
"INTEGRATIONS_DETAIL": "SigNoz | Integration",
|
||||
"PUBLIC_DASHBOARD": "SigNoz | Dashboard",
|
||||
"LLM_OBSERVABILITY_OVERVIEW": "SigNoz | LLM Observability Overview",
|
||||
"LLM_OBSERVABILITY_CONFIGURATION": "SigNoz | LLM Observability Configuration"
|
||||
}
|
||||
"LLM_OBSERVABILITY_BASE": "SigNoz | LLM Observability",
|
||||
"LLM_OBSERVABILITY_MODEL_PRICING": "SigNoz | Model Pricing"
|
||||
}
|
||||
|
||||
@@ -329,3 +329,10 @@ export const LLMObservabilityPage = Loadable(
|
||||
/* webpackChunkName: "LLM Observability Page" */ 'pages/LLMObservability'
|
||||
),
|
||||
);
|
||||
|
||||
export const LLMObservabilityModelPricingPage = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "LLM Observability Model Pricing Page" */ 'pages/LLMObservabilityModelPricing'
|
||||
),
|
||||
);
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
LicensePage,
|
||||
ListAllALertsPage,
|
||||
LLMObservabilityPage,
|
||||
LLMObservabilityModelPricingPage,
|
||||
LiveLogs,
|
||||
Login,
|
||||
Logs,
|
||||
@@ -514,24 +515,17 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LLM_OBSERVABILITY_ATTRIBUTE_MAPPING,
|
||||
path: ROUTES.LLM_OBSERVABILITY_BASE,
|
||||
exact: true,
|
||||
component: LLMObservabilityPage,
|
||||
key: 'LLM_OBSERVABILITY_ATTRIBUTE_MAPPING',
|
||||
key: 'LLM_OBSERVABILITY_BASE',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LLM_OBSERVABILITY_OVERVIEW,
|
||||
path: ROUTES.LLM_OBSERVABILITY_MODEL_PRICING,
|
||||
exact: true,
|
||||
component: LLMObservabilityPage,
|
||||
key: 'LLM_OBSERVABILITY_OVERVIEW',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LLM_OBSERVABILITY_CONFIGURATION,
|
||||
exact: true,
|
||||
component: LLMObservabilityPage,
|
||||
key: 'LLM_OBSERVABILITY_CONFIGURATION',
|
||||
component: LLMObservabilityModelPricingPage,
|
||||
key: 'LLM_OBSERVABILITY_MODEL_PRICING',
|
||||
isPrivate: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -269,170 +269,3 @@ export function mockQueryRangeV5WithEventsResponse({
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export type MockTracesOptions = {
|
||||
offset?: number;
|
||||
pageSize?: number;
|
||||
hasMore?: boolean;
|
||||
delay?: number;
|
||||
customTraces?: Array<{
|
||||
serviceName?: string;
|
||||
name?: string;
|
||||
durationNano?: number;
|
||||
httpMethod?: string;
|
||||
responseStatusCode?: string;
|
||||
}>;
|
||||
onReceiveRequest?: (
|
||||
req: RestRequest,
|
||||
) =>
|
||||
| undefined
|
||||
| void
|
||||
| Omit<MockTracesOptions, 'onReceiveRequest'>
|
||||
| Promise<Omit<MockTracesOptions, 'onReceiveRequest'>>
|
||||
| Promise<void>;
|
||||
};
|
||||
|
||||
const createTracesResponse = ({
|
||||
offset = 0,
|
||||
pageSize = 10,
|
||||
hasMore = true,
|
||||
customTraces,
|
||||
}: MockTracesOptions): MetricRangePayloadV5 => {
|
||||
const serviceNames = ['frontend', 'backend', 'database', 'api-gateway'];
|
||||
const spanNames = [
|
||||
'GET /api/users',
|
||||
'POST /api/orders',
|
||||
'SELECT * FROM users',
|
||||
];
|
||||
const httpMethods = ['GET', 'POST', 'PUT', 'DELETE'];
|
||||
const statusCodes = ['200', '201', '400', '404', '500'];
|
||||
|
||||
const rows = customTraces
|
||||
? customTraces.map((trace, index) => {
|
||||
const baseTimestamp = new Date('2026-04-21T17:54:33Z').getTime();
|
||||
const currentTimestamp = new Date(baseTimestamp - index * 60000);
|
||||
return {
|
||||
timestamp: currentTimestamp.toISOString(),
|
||||
data: {
|
||||
serviceName:
|
||||
trace.serviceName ?? serviceNames[index % serviceNames.length],
|
||||
name: trace.name ?? spanNames[index % spanNames.length],
|
||||
durationNano: trace.durationNano ?? 1000000 + index * 100000,
|
||||
httpMethod: trace.httpMethod ?? httpMethods[index % httpMethods.length],
|
||||
responseStatusCode:
|
||||
trace.responseStatusCode ?? statusCodes[index % statusCodes.length],
|
||||
traceID: `trace-id-${index}`,
|
||||
spanID: `span-id-${index}`,
|
||||
},
|
||||
};
|
||||
})
|
||||
: Array.from(
|
||||
{ length: hasMore ? pageSize : Math.ceil(pageSize / 2) },
|
||||
(_, index) => {
|
||||
const cumulativeIndex = offset + index;
|
||||
const baseTimestamp = new Date('2026-04-21T17:54:33Z').getTime();
|
||||
const currentTimestamp = new Date(baseTimestamp - cumulativeIndex * 60000);
|
||||
return {
|
||||
timestamp: currentTimestamp.toISOString(),
|
||||
data: {
|
||||
serviceName: serviceNames[cumulativeIndex % serviceNames.length],
|
||||
name: spanNames[cumulativeIndex % spanNames.length],
|
||||
durationNano: 1000000 + cumulativeIndex * 100000,
|
||||
httpMethod: httpMethods[cumulativeIndex % httpMethods.length],
|
||||
responseStatusCode: statusCodes[cumulativeIndex % statusCodes.length],
|
||||
traceID: `trace-id-${cumulativeIndex}`,
|
||||
spanID: `span-id-${cumulativeIndex}`,
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
data: {
|
||||
type: 'raw',
|
||||
data: {
|
||||
results: [
|
||||
{
|
||||
queryName: 'A',
|
||||
nextCursor: hasMore ? 'next-cursor-token' : '',
|
||||
rows,
|
||||
},
|
||||
],
|
||||
},
|
||||
meta: {
|
||||
bytesScanned: 9682976,
|
||||
durationMs: 295,
|
||||
rowsScanned: 34198,
|
||||
stepIntervals: { A: 170 },
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export function mockQueryRangeV5WithTracesResponse({
|
||||
hasMore = true,
|
||||
offset = 0,
|
||||
pageSize = 10,
|
||||
delay = 0,
|
||||
customTraces,
|
||||
onReceiveRequest,
|
||||
}: MockTracesOptions = {}): void {
|
||||
server.use(
|
||||
rest.post(QUERY_RANGE_URL, async (req, res, ctx) =>
|
||||
res(
|
||||
...(delay ? [ctx.delay(delay)] : []),
|
||||
ctx.status(200),
|
||||
ctx.json(
|
||||
createTracesResponse(
|
||||
(await onReceiveRequest?.(req)) ?? {
|
||||
hasMore,
|
||||
pageSize,
|
||||
offset,
|
||||
customTraces,
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function mockQueryRangeV5WithEmptyTraces(): void {
|
||||
server.use(
|
||||
rest.post(QUERY_RANGE_URL, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: {
|
||||
type: 'raw',
|
||||
data: {
|
||||
results: [{ queryName: 'A', nextCursor: '', rows: [] }],
|
||||
},
|
||||
meta: {
|
||||
bytesScanned: 0,
|
||||
durationMs: 10,
|
||||
rowsScanned: 0,
|
||||
stepIntervals: {},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function mockQueryRangeV5WithKeyNotFoundError(): void {
|
||||
server.use(
|
||||
rest.post(QUERY_RANGE_URL, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(400),
|
||||
ctx.json({
|
||||
error: {
|
||||
code: 'invalid_input',
|
||||
errors: [{ message: 'key not found' }],
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import type {
|
||||
GetChecks200,
|
||||
GetChecksParams,
|
||||
InframonitoringtypesPostableClustersDTO,
|
||||
InframonitoringtypesPostableContainersDTO,
|
||||
InframonitoringtypesPostableDaemonSetsDTO,
|
||||
InframonitoringtypesPostableDeploymentsDTO,
|
||||
InframonitoringtypesPostableHostsDTO,
|
||||
@@ -32,7 +31,6 @@ import type {
|
||||
InframonitoringtypesPostableStatefulSetsDTO,
|
||||
InframonitoringtypesPostableVolumesDTO,
|
||||
ListClusters200,
|
||||
ListContainers200,
|
||||
ListDaemonSets200,
|
||||
ListDeployments200,
|
||||
ListHosts200,
|
||||
@@ -550,89 +548,6 @@ export const useListJobs = <
|
||||
> => {
|
||||
return useMutation(getListJobsMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* Returns a paginated list of Kubernetes containers with key kubeletstats metrics: CPU usage (cores), CPU request/limit utilization, memory working set, and memory request/limit utilization. Each container also reports health signals from the k8s_cluster receiver: status (kubectl-style display status derived from k8s.container.status.state + k8s.container.status.reason), restarts (absolute count from k8s.container.restarts), and ready (ready/not_ready from k8s.container.ready). The row identity is (k8s.pod.uid, k8s.container.name), stable across container restarts. Each container includes metadata attributes (k8s.container.name, k8s.pod.name, container.image.name, container.image.tag, k8s.namespace.name, k8s.node.name, k8s.cluster.name, and workload owner such as deployment/statefulset/daemonset/job). The response type is 'list' for the default (k8s.pod.uid, k8s.container.name) grouping (each row is one container with its current status and ready state) or 'grouped_list' for custom groupBy keys (each row aggregates containers in the group with per-status counts under containerCountsByStatus, per-readiness counts under containerCountsByReady, and restarts as the group sum). Status requires the optional k8s.container.status.state and k8s.container.status.reason metrics; when either is missing, status is omitted and a warning is returned while restarts and ready are still computed. Supports filtering via a filter expression, custom groupBy, ordering by any of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (cpu, cpuRequestUtilization, cpuLimitUtilization, memory, memoryRequestUtilization, memoryLimitUtilization) and restarts return -1 as a sentinel when no data is available for that field.
|
||||
* @summary List Kubernetes Containers for Infra Monitoring
|
||||
*/
|
||||
export const listContainers = (
|
||||
inframonitoringtypesPostableContainersDTO?: BodyType<InframonitoringtypesPostableContainersDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListContainers200>({
|
||||
url: `/api/v2/infra_monitoring/kube_containers`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: inframonitoringtypesPostableContainersDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListContainersMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listContainers>>,
|
||||
TError,
|
||||
{ data?: BodyType<InframonitoringtypesPostableContainersDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listContainers>>,
|
||||
TError,
|
||||
{ data?: BodyType<InframonitoringtypesPostableContainersDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['listContainers'];
|
||||
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 listContainers>>,
|
||||
{ data?: BodyType<InframonitoringtypesPostableContainersDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return listContainers(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ListContainersMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listContainers>>
|
||||
>;
|
||||
export type ListContainersMutationBody =
|
||||
| BodyType<InframonitoringtypesPostableContainersDTO>
|
||||
| undefined;
|
||||
export type ListContainersMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List Kubernetes Containers for Infra Monitoring
|
||||
*/
|
||||
export const useListContainers = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listContainers>>,
|
||||
TError,
|
||||
{ data?: BodyType<InframonitoringtypesPostableContainersDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof listContainers>>,
|
||||
TError,
|
||||
{ data?: BodyType<InframonitoringtypesPostableContainersDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getListContainersMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* Returns a paginated list of Kubernetes namespaces with key aggregated pod metrics: CPU usage and memory working set (summed across pods in the group), plus per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value in the window). Each namespace includes metadata attributes (k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.namespace.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / memory, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (namespaceCPU, namespaceMemory) return -1 as a sentinel when no data is available for that field.
|
||||
* @summary List Namespaces for Infra Monitoring
|
||||
|
||||
@@ -22,16 +22,12 @@ import type {
|
||||
CreateServiceAccountKey201,
|
||||
CreateServiceAccountKeyPathParameters,
|
||||
CreateServiceAccountRole201,
|
||||
CreateServiceAccountRoleDeprecated201,
|
||||
CreateServiceAccountRoleDeprecatedPathParameters,
|
||||
CreateServiceAccountRolePathParameters,
|
||||
DeleteServiceAccountPathParameters,
|
||||
DeleteServiceAccountRoleDeprecatedPathParameters,
|
||||
DeleteServiceAccountRolePathParameters,
|
||||
GetMyServiceAccount200,
|
||||
GetServiceAccount200,
|
||||
GetServiceAccountPathParameters,
|
||||
GetServiceAccountRole200,
|
||||
GetServiceAccountRolePathParameters,
|
||||
GetServiceAccountRoles200,
|
||||
GetServiceAccountRolesPathParameters,
|
||||
ListServiceAccountKeys200,
|
||||
@@ -39,7 +35,6 @@ import type {
|
||||
ListServiceAccounts200,
|
||||
RenderErrorResponseDTO,
|
||||
RevokeServiceAccountKeyPathParameters,
|
||||
ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO,
|
||||
ServiceaccounttypesPostableFactorAPIKeyDTO,
|
||||
ServiceaccounttypesPostableServiceAccountDTO,
|
||||
ServiceaccounttypesPostableServiceAccountRoleDTO,
|
||||
@@ -51,272 +46,6 @@ import type {
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
|
||||
|
||||
/**
|
||||
* This endpoint assigns a role to a service account
|
||||
* @summary Create service account role
|
||||
*/
|
||||
export const createServiceAccountRole = (
|
||||
serviceaccounttypesPostableServiceAccountRoleDTO?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateServiceAccountRole201>({
|
||||
url: `/api/v1/service_account_roles`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: serviceaccounttypesPostableServiceAccountRoleDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateServiceAccountRoleMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||
TError,
|
||||
{ data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||
TError,
|
||||
{ data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createServiceAccountRole'];
|
||||
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 createServiceAccountRole>>,
|
||||
{ data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createServiceAccountRole(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateServiceAccountRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>
|
||||
>;
|
||||
export type CreateServiceAccountRoleMutationBody =
|
||||
| BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>
|
||||
| undefined;
|
||||
export type CreateServiceAccountRoleMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create service account role
|
||||
*/
|
||||
export const useCreateServiceAccountRole = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||
TError,
|
||||
{ data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||
TError,
|
||||
{ data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getCreateServiceAccountRoleMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint revokes a role from a service account
|
||||
* @summary Delete service account role
|
||||
*/
|
||||
export const deleteServiceAccountRole = (
|
||||
{ id }: DeleteServiceAccountRolePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/service_account_roles/${id}`,
|
||||
method: 'DELETE',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteServiceAccountRoleMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteServiceAccountRole'];
|
||||
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 deleteServiceAccountRole>>,
|
||||
{ pathParams: DeleteServiceAccountRolePathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteServiceAccountRole(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteServiceAccountRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>
|
||||
>;
|
||||
|
||||
export type DeleteServiceAccountRoleMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete service account role
|
||||
*/
|
||||
export const useDeleteServiceAccountRole = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getDeleteServiceAccountRoleMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint gets an existing service account role
|
||||
* @summary Get service account role
|
||||
*/
|
||||
export const getServiceAccountRole = (
|
||||
{ id }: GetServiceAccountRolePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetServiceAccountRole200>({
|
||||
url: `/api/v1/service_account_roles/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetServiceAccountRoleQueryKey = ({
|
||||
id,
|
||||
}: GetServiceAccountRolePathParameters) => {
|
||||
return [`/api/v1/service_account_roles/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetServiceAccountRoleQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getServiceAccountRole>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetServiceAccountRolePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getServiceAccountRole>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetServiceAccountRoleQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getServiceAccountRole>>
|
||||
> = ({ signal }) => getServiceAccountRole({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getServiceAccountRole>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetServiceAccountRoleQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getServiceAccountRole>>
|
||||
>;
|
||||
export type GetServiceAccountRoleQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get service account role
|
||||
*/
|
||||
|
||||
export function useGetServiceAccountRole<
|
||||
TData = Awaited<ReturnType<typeof getServiceAccountRole>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetServiceAccountRolePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getServiceAccountRole>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetServiceAccountRoleQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get service account role
|
||||
*/
|
||||
export const invalidateGetServiceAccountRole = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetServiceAccountRolePathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetServiceAccountRoleQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint lists the service accounts for an organisation
|
||||
* @summary List service accounts
|
||||
@@ -1255,46 +984,45 @@ export const invalidateGetServiceAccountRoles = async (
|
||||
|
||||
/**
|
||||
* This endpoint assigns a role to a service account
|
||||
* @deprecated
|
||||
* @summary Create service account role
|
||||
*/
|
||||
export const createServiceAccountRoleDeprecated = (
|
||||
{ id }: CreateServiceAccountRoleDeprecatedPathParameters,
|
||||
serviceaccounttypesDeprecatedPostableServiceAccountRoleDTO?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>,
|
||||
export const createServiceAccountRole = (
|
||||
{ id }: CreateServiceAccountRolePathParameters,
|
||||
serviceaccounttypesPostableServiceAccountRoleDTO?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateServiceAccountRoleDeprecated201>({
|
||||
return GeneratedAPIInstance<CreateServiceAccountRole201>({
|
||||
url: `/api/v1/service_accounts/${id}/roles`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: serviceaccounttypesDeprecatedPostableServiceAccountRoleDTO,
|
||||
data: serviceaccounttypesPostableServiceAccountRoleDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateServiceAccountRoleDeprecatedMutationOptions = <
|
||||
export const getCreateServiceAccountRoleMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>,
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateServiceAccountRoleDeprecatedPathParameters;
|
||||
data?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>;
|
||||
pathParams: CreateServiceAccountRolePathParameters;
|
||||
data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>,
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateServiceAccountRoleDeprecatedPathParameters;
|
||||
data?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>;
|
||||
pathParams: CreateServiceAccountRolePathParameters;
|
||||
data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createServiceAccountRoleDeprecated'];
|
||||
const mutationKey = ['createServiceAccountRole'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
@@ -1304,66 +1032,62 @@ export const getCreateServiceAccountRoleDeprecatedMutationOptions = <
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>,
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||
{
|
||||
pathParams: CreateServiceAccountRoleDeprecatedPathParameters;
|
||||
data?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>;
|
||||
pathParams: CreateServiceAccountRolePathParameters;
|
||||
data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return createServiceAccountRoleDeprecated(pathParams, data);
|
||||
return createServiceAccountRole(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateServiceAccountRoleDeprecatedMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>
|
||||
export type CreateServiceAccountRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>
|
||||
>;
|
||||
export type CreateServiceAccountRoleDeprecatedMutationBody =
|
||||
| BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>
|
||||
export type CreateServiceAccountRoleMutationBody =
|
||||
| BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>
|
||||
| undefined;
|
||||
export type CreateServiceAccountRoleDeprecatedMutationError =
|
||||
export type CreateServiceAccountRoleMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Create service account role
|
||||
*/
|
||||
export const useCreateServiceAccountRoleDeprecated = <
|
||||
export const useCreateServiceAccountRole = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>,
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateServiceAccountRoleDeprecatedPathParameters;
|
||||
data?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>;
|
||||
pathParams: CreateServiceAccountRolePathParameters;
|
||||
data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>,
|
||||
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateServiceAccountRoleDeprecatedPathParameters;
|
||||
data?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>;
|
||||
pathParams: CreateServiceAccountRolePathParameters;
|
||||
data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(
|
||||
getCreateServiceAccountRoleDeprecatedMutationOptions(options),
|
||||
);
|
||||
return useMutation(getCreateServiceAccountRoleMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint revokes a role from service account
|
||||
* @deprecated
|
||||
* @summary Delete service account role
|
||||
*/
|
||||
export const deleteServiceAccountRoleDeprecated = (
|
||||
{ id, rid }: DeleteServiceAccountRoleDeprecatedPathParameters,
|
||||
export const deleteServiceAccountRole = (
|
||||
{ id, rid }: DeleteServiceAccountRolePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
@@ -1373,23 +1097,23 @@ export const deleteServiceAccountRoleDeprecated = (
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteServiceAccountRoleDeprecatedMutationOptions = <
|
||||
export const getDeleteServiceAccountRoleMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>,
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteServiceAccountRoleDeprecatedPathParameters },
|
||||
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>,
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteServiceAccountRoleDeprecatedPathParameters },
|
||||
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteServiceAccountRoleDeprecated'];
|
||||
const mutationKey = ['deleteServiceAccountRole'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
@@ -1399,47 +1123,44 @@ export const getDeleteServiceAccountRoleDeprecatedMutationOptions = <
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>,
|
||||
{ pathParams: DeleteServiceAccountRoleDeprecatedPathParameters }
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||
{ pathParams: DeleteServiceAccountRolePathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteServiceAccountRoleDeprecated(pathParams);
|
||||
return deleteServiceAccountRole(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteServiceAccountRoleDeprecatedMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>
|
||||
export type DeleteServiceAccountRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>
|
||||
>;
|
||||
|
||||
export type DeleteServiceAccountRoleDeprecatedMutationError =
|
||||
export type DeleteServiceAccountRoleMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Delete service account role
|
||||
*/
|
||||
export const useDeleteServiceAccountRoleDeprecated = <
|
||||
export const useDeleteServiceAccountRole = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>,
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteServiceAccountRoleDeprecatedPathParameters },
|
||||
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>,
|
||||
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteServiceAccountRoleDeprecatedPathParameters },
|
||||
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(
|
||||
getDeleteServiceAccountRoleDeprecatedMutationOptions(options),
|
||||
);
|
||||
return useMutation(getDeleteServiceAccountRoleMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint gets my service account
|
||||
|
||||
@@ -2048,13 +2048,6 @@ export interface AuthtypesAuthNSupportDTO {
|
||||
password?: AuthtypesPasswordAuthNSupportDTO[] | null;
|
||||
}
|
||||
|
||||
export interface AuthtypesDeprecatedPostableUserRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesGettableAuthDomainDTO {
|
||||
authNProviderInfo?: AuthtypesAuthNProviderInfoDTO;
|
||||
config?: AuthtypesAuthDomainConfigDTO;
|
||||
@@ -2294,6 +2287,13 @@ export interface AuthtypesPostableRotateTokenDTO {
|
||||
refreshToken?: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesPostableUserRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesPostableUserDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -2310,18 +2310,7 @@ export interface AuthtypesPostableUserDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
userRoles?: AuthtypesDeprecatedPostableUserRoleDTO[];
|
||||
}
|
||||
|
||||
export interface AuthtypesPostableUserRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
roleId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
userId: string;
|
||||
userRoles?: AuthtypesPostableUserRoleDTO[];
|
||||
}
|
||||
|
||||
export interface AuthtypesRoleDTO {
|
||||
@@ -5577,7 +5566,6 @@ export enum InframonitoringtypesCheckTypeDTO {
|
||||
namespaces = 'namespaces',
|
||||
clusters = 'clusters',
|
||||
volumes = 'volumes',
|
||||
kube_containers = 'kube_containers',
|
||||
}
|
||||
export interface InframonitoringtypesMissingMetricsComponentEntryDTO {
|
||||
associatedComponent: InframonitoringtypesAssociatedComponentDTO;
|
||||
@@ -5705,81 +5693,6 @@ export interface InframonitoringtypesPodCountsByPhaseDTO {
|
||||
unknown: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPodCountsByStatusDTO {
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
completed: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
containerCannotRun: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
containerCreating: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
crashLoopBackOff: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
createContainerConfigError: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
errImagePull: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
error: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
evicted: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
failed: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
imagePullBackOff: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
nodeAffinity: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
nodeLost: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
oomKilled: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
pending: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
running: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
shutdown: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
unexpectedAdmissionError: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
unknown: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesClusterRecordDTO {
|
||||
/**
|
||||
* @type number
|
||||
@@ -5811,7 +5724,6 @@ export interface InframonitoringtypesClusterRecordDTO {
|
||||
meta: InframonitoringtypesClusterRecordDTOMeta;
|
||||
nodeCountsByReadiness: InframonitoringtypesNodeCountsByReadinessDTO;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesResponseTypeDTO {
|
||||
@@ -5857,174 +5769,6 @@ export interface InframonitoringtypesClustersDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesContainerCountsByReadyDTO {
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
notReady: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
ready: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesContainerCountsByStatusDTO {
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
completed: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
containerCannotRun: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
containerCreating: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
crashLoopBackOff: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
createContainerConfigError: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
errImagePull: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
error: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
imagePullBackOff: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
oomKilled: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
running: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
terminated: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
unknown: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
waiting: number;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesContainerReadyDTO {
|
||||
ready = 'ready',
|
||||
not_ready = 'not_ready',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
export type InframonitoringtypesContainerRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesContainerRecordDTOMeta =
|
||||
InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
|
||||
|
||||
export enum InframonitoringtypesContainerStatusDTO {
|
||||
running = 'running',
|
||||
waiting = 'waiting',
|
||||
terminated = 'terminated',
|
||||
crashloopbackoff = 'crashloopbackoff',
|
||||
imagepullbackoff = 'imagepullbackoff',
|
||||
errimagepull = 'errimagepull',
|
||||
createcontainerconfigerror = 'createcontainerconfigerror',
|
||||
containercreating = 'containercreating',
|
||||
oomkilled = 'oomkilled',
|
||||
completed = 'completed',
|
||||
error = 'error',
|
||||
containercannotrun = 'containercannotrun',
|
||||
unknown = 'unknown',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
export interface InframonitoringtypesContainerRecordDTO {
|
||||
containerCountsByReady: InframonitoringtypesContainerCountsByReadyDTO;
|
||||
containerCountsByStatus: InframonitoringtypesContainerCountsByStatusDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
containerName: string;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
cpu: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
cpuLimitUtilization: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
cpuRequestUtilization: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
memory: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
memoryLimitUtilization: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
memoryRequestUtilization: number;
|
||||
/**
|
||||
* @type object,null
|
||||
*/
|
||||
meta: InframonitoringtypesContainerRecordDTOMeta;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
podUID: string;
|
||||
ready: InframonitoringtypesContainerReadyDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
restarts: number;
|
||||
status: InframonitoringtypesContainerStatusDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesContainersDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesContainerRecordDTO[];
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
total: number;
|
||||
type: InframonitoringtypesResponseTypeDTO;
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export type InframonitoringtypesDaemonSetRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -6083,7 +5827,6 @@ export interface InframonitoringtypesDaemonSetRecordDTO {
|
||||
*/
|
||||
meta: InframonitoringtypesDaemonSetRecordDTOMeta;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesDaemonSetsDTO {
|
||||
@@ -6161,7 +5904,6 @@ export interface InframonitoringtypesDeploymentRecordDTO {
|
||||
*/
|
||||
meta: InframonitoringtypesDeploymentRecordDTOMeta;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesDeploymentsDTO {
|
||||
@@ -6328,7 +6070,6 @@ export interface InframonitoringtypesJobRecordDTO {
|
||||
*/
|
||||
meta: InframonitoringtypesJobRecordDTOMeta;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
@@ -6382,7 +6123,6 @@ export interface InframonitoringtypesNamespaceRecordDTO {
|
||||
*/
|
||||
namespaceName: string;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesNamespacesDTO {
|
||||
@@ -6449,7 +6189,6 @@ export interface InframonitoringtypesNodeRecordDTO {
|
||||
*/
|
||||
nodeName: string;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesNodesDTO {
|
||||
@@ -6487,27 +6226,6 @@ export type InframonitoringtypesPodRecordDTOMetaAnyOf = {
|
||||
export type InframonitoringtypesPodRecordDTOMeta =
|
||||
InframonitoringtypesPodRecordDTOMetaAnyOf | null;
|
||||
|
||||
export enum InframonitoringtypesPodStatusDTO {
|
||||
pending = 'pending',
|
||||
running = 'running',
|
||||
failed = 'failed',
|
||||
unknown = 'unknown',
|
||||
crashloopbackoff = 'crashloopbackoff',
|
||||
imagepullbackoff = 'imagepullbackoff',
|
||||
errimagepull = 'errimagepull',
|
||||
createcontainerconfigerror = 'createcontainerconfigerror',
|
||||
containercreating = 'containercreating',
|
||||
oomkilled = 'oomkilled',
|
||||
completed = 'completed',
|
||||
error = 'error',
|
||||
containercannotrun = 'containercannotrun',
|
||||
evicted = 'evicted',
|
||||
nodeaffinity = 'nodeaffinity',
|
||||
nodelost = 'nodelost',
|
||||
shutdown = 'shutdown',
|
||||
unexpectedadmissionerror = 'unexpectedadmissionerror',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
export interface InframonitoringtypesPodRecordDTO {
|
||||
/**
|
||||
* @type object,null
|
||||
@@ -6534,7 +6252,6 @@ export interface InframonitoringtypesPodRecordDTO {
|
||||
*/
|
||||
podCPURequest: number;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
@@ -6551,12 +6268,6 @@ export interface InframonitoringtypesPodRecordDTO {
|
||||
*/
|
||||
podMemoryRequest: number;
|
||||
podPhase: InframonitoringtypesPodPhaseDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
podRestarts: number;
|
||||
podStatus: InframonitoringtypesPodStatusDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -6607,33 +6318,6 @@ export interface InframonitoringtypesPostableClustersDTO {
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPostableContainersDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
groupBy?: Querybuildertypesv5GroupByKeyDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
offset?: number;
|
||||
orderBy?: Querybuildertypesv5OrderByDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPostableDaemonSetsDTO {
|
||||
/**
|
||||
* @type integer
|
||||
@@ -6901,7 +6585,6 @@ export interface InframonitoringtypesStatefulSetRecordDTO {
|
||||
*/
|
||||
meta: InframonitoringtypesStatefulSetRecordDTOMeta;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
@@ -8825,13 +8508,6 @@ export interface RuletypesRuleDTO {
|
||||
export enum RuletypesThresholdKindDTO {
|
||||
basic = 'basic',
|
||||
}
|
||||
export interface ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ServiceaccounttypesGettableFactorAPIKeyDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -8901,11 +8577,7 @@ export interface ServiceaccounttypesPostableServiceAccountRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
roleId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
serviceAccountId: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ServiceaccounttypesServiceAccountDTO {
|
||||
@@ -10671,28 +10343,6 @@ export type UpdateRoutePolicy200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateServiceAccountRole201 = {
|
||||
data: TypesIdentifiableDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteServiceAccountRolePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetServiceAccountRolePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetServiceAccountRole200 = {
|
||||
data: ServiceaccounttypesServiceAccountRoleDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListServiceAccounts200 = {
|
||||
/**
|
||||
* @type array
|
||||
@@ -10776,10 +10426,10 @@ export type GetServiceAccountRoles200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateServiceAccountRoleDeprecatedPathParameters = {
|
||||
export type CreateServiceAccountRolePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type CreateServiceAccountRoleDeprecated201 = {
|
||||
export type CreateServiceAccountRole201 = {
|
||||
data: TypesIdentifiableDTO;
|
||||
/**
|
||||
* @type string
|
||||
@@ -10787,7 +10437,7 @@ export type CreateServiceAccountRoleDeprecated201 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteServiceAccountRoleDeprecatedPathParameters = {
|
||||
export type DeleteServiceAccountRolePathParameters = {
|
||||
id: string;
|
||||
rid: string;
|
||||
};
|
||||
@@ -11237,14 +10887,6 @@ export type ListJobs200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListContainers200 = {
|
||||
data: InframonitoringtypesContainersDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListNamespaces200 = {
|
||||
data: InframonitoringtypesNamespacesDTO;
|
||||
/**
|
||||
@@ -11955,28 +11597,6 @@ export type RotateSession200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateUserRole201 = {
|
||||
data: TypesIdentifiableDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteUserRolePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetUserRolePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetUserRole200 = {
|
||||
data: AuthtypesUserRoleDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListUsers200 = {
|
||||
/**
|
||||
* @type array
|
||||
|
||||
@@ -19,15 +19,12 @@ import type {
|
||||
|
||||
import type {
|
||||
AuthtypesPostableUserDTO,
|
||||
AuthtypesPostableUserRoleDTO,
|
||||
CreateInvite201,
|
||||
CreateResetPasswordToken201,
|
||||
CreateResetPasswordTokenPathParameters,
|
||||
CreateUser201,
|
||||
CreateUserRole201,
|
||||
DeleteUserDeprecatedPathParameters,
|
||||
DeleteUserPathParameters,
|
||||
DeleteUserRolePathParameters,
|
||||
GetMyUser200,
|
||||
GetMyUserDeprecated200,
|
||||
GetResetPasswordToken200,
|
||||
@@ -40,8 +37,6 @@ import type {
|
||||
GetUserDeprecated200,
|
||||
GetUserDeprecatedPathParameters,
|
||||
GetUserPathParameters,
|
||||
GetUserRole200,
|
||||
GetUserRolePathParameters,
|
||||
GetUsersByRoleID200,
|
||||
GetUsersByRoleIDPathParameters,
|
||||
ListUsers200,
|
||||
@@ -1159,267 +1154,6 @@ export const invalidateGetUsersByRoleID = async (
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint assigns a role to a user
|
||||
* @summary Create user role
|
||||
*/
|
||||
export const createUserRole = (
|
||||
authtypesPostableUserRoleDTO?: BodyType<AuthtypesPostableUserRoleDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateUserRole201>({
|
||||
url: `/api/v2/user_roles`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: authtypesPostableUserRoleDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateUserRoleMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createUserRole>>,
|
||||
TError,
|
||||
{ data?: BodyType<AuthtypesPostableUserRoleDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createUserRole>>,
|
||||
TError,
|
||||
{ data?: BodyType<AuthtypesPostableUserRoleDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createUserRole'];
|
||||
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 createUserRole>>,
|
||||
{ data?: BodyType<AuthtypesPostableUserRoleDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createUserRole(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateUserRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createUserRole>>
|
||||
>;
|
||||
export type CreateUserRoleMutationBody =
|
||||
| BodyType<AuthtypesPostableUserRoleDTO>
|
||||
| undefined;
|
||||
export type CreateUserRoleMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create user role
|
||||
*/
|
||||
export const useCreateUserRole = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createUserRole>>,
|
||||
TError,
|
||||
{ data?: BodyType<AuthtypesPostableUserRoleDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createUserRole>>,
|
||||
TError,
|
||||
{ data?: BodyType<AuthtypesPostableUserRoleDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getCreateUserRoleMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint revokes a role from a user
|
||||
* @summary Delete user role
|
||||
*/
|
||||
export const deleteUserRole = (
|
||||
{ id }: DeleteUserRolePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v2/user_roles/${id}`,
|
||||
method: 'DELETE',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteUserRoleMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteUserRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteUserRolePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteUserRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteUserRolePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteUserRole'];
|
||||
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 deleteUserRole>>,
|
||||
{ pathParams: DeleteUserRolePathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteUserRole(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteUserRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteUserRole>>
|
||||
>;
|
||||
|
||||
export type DeleteUserRoleMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete user role
|
||||
*/
|
||||
export const useDeleteUserRole = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteUserRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteUserRolePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteUserRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteUserRolePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getDeleteUserRoleMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint gets an existing user role
|
||||
* @summary Get user role
|
||||
*/
|
||||
export const getUserRole = (
|
||||
{ id }: GetUserRolePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetUserRole200>({
|
||||
url: `/api/v2/user_roles/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetUserRoleQueryKey = ({ id }: GetUserRolePathParameters) => {
|
||||
return [`/api/v2/user_roles/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetUserRoleQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getUserRole>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetUserRolePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getUserRole>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetUserRoleQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getUserRole>>> = ({
|
||||
signal,
|
||||
}) => getUserRole({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getUserRole>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetUserRoleQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getUserRole>>
|
||||
>;
|
||||
export type GetUserRoleQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get user role
|
||||
*/
|
||||
|
||||
export function useGetUserRole<
|
||||
TData = Awaited<ReturnType<typeof getUserRole>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetUserRolePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getUserRole>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetUserRoleQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get user role
|
||||
*/
|
||||
export const invalidateGetUserRole = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetUserRolePathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetUserRoleQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint lists all users for the organization
|
||||
* @summary List users v2
|
||||
@@ -2131,7 +1865,6 @@ export const invalidateGetRolesByUserID = async (
|
||||
|
||||
/**
|
||||
* This endpoint assigns the role to the user roles by user id
|
||||
* @deprecated
|
||||
* @summary Set user roles
|
||||
*/
|
||||
export const setRoleByUserID = (
|
||||
@@ -2203,7 +1936,6 @@ export type SetRoleByUserIDMutationBody =
|
||||
export type SetRoleByUserIDMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Set user roles
|
||||
*/
|
||||
export const useSetRoleByUserID = <
|
||||
@@ -2232,7 +1964,6 @@ export const useSetRoleByUserID = <
|
||||
};
|
||||
/**
|
||||
* This endpoint removes a role from the user by user id and role id
|
||||
* @deprecated
|
||||
* @summary Remove a role from user
|
||||
*/
|
||||
export const removeUserRoleByUserIDAndRoleID = (
|
||||
@@ -2291,7 +2022,6 @@ export type RemoveUserRoleByUserIDAndRoleIDMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Remove a role from user
|
||||
*/
|
||||
export const useRemoveUserRoleByUserIDAndRoleID = <
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#1287b1" viewBox="0 0 24 24"><title>Apache Cassandra</title><path d="M10.374 10.53a3 3 0 0 1-.428-.222l.555.143c0 .02-.01.036-.01.055l-.117.025zm-.283 1.506-.315.253.852-1.079-1.078.391c.002.017.009.033.009.05a.57.57 0 0 1-.184.42q.154.327.375.616a3.2 3.2 0 0 1 .34-.651zm.717-2.347-.652-.82a.43.43 0 0 1-.506.162c-.054.073-.083.162-.13.24l1.258.463zm-1.666.444c-.07.314-.087.637-.05.956a.57.57 0 0 1 .451.475l.946-.606c-.067-.022-.126-.06-.191-.088l-1.119-.08.64-.14a3.2 3.2 0 0 1-.668-.554zM20.1 11.648c-.164.202.833 1.022.833 1.022s-1.654-1.022-2.234-.72c-.278.144.574.811 1.175 1.242-.428-.274-.982-.571-1.175-.408-.328.277 1.565 2.549 1.565 2.549s-2.145-2.322-2.36-2.209c-.214.114.593 1.224.593 1.224s-1.06-1.16-1.35-.959c-.29.202 1.514 3.218 1.514 3.218s-1.956-3.091-2.763-2.574c1.268 2.782.795 3.18.795 3.18s-.162-2.839-1.742-2.764c-.795.038.379 2.12.379 2.12s-1.08-1.902-1.8-1.864c1.326 2.51.854 3.53.854 3.53s.219-2.143-1.58-3.336c.682.606-.427 3.336-.427 3.336s.976-4.023-.719-3.256c-.268.121-.019 2.007-.019 2.007s-.34-2.158-.851-2.045c-.298.066-1.893 2.99-1.893 2.99s1.306-3.16.908-3.027c-.29.096-.833 1.4-.833 1.4s.265-1.287 0-1.363c-.264-.075-1.74 1.363-1.74 1.363s1.097-1.287.908-1.552c-.287-.402-.623-.42-1.022-.265-.581.226-1.363 1.287-1.363 1.287s.78-1.074.643-1.476c-.219-.647-2.46 1.249-2.46 1.249s1.325-1.25 1.022-1.514c-.303-.265-1.947-.183-2.46-.185-1.515-.004-2.039-.36-2.498-.724 1.987.997 3.803-.151 6.094.494l.21.06c-1.3-.558-2.144-1.378-2.226-2.354-.036-.416.074-.827.297-1.222.619-.4 1.29-.773 2.06-1.095a4 4 0 0 0-.064.698c0 2.44 2.203 4.417 4.92 4.417s4.92-1.977 4.92-4.417c0-.45-.083-.881-.223-1.29 1.431.404 2.45.968 3.132 1.335.022.092.045.184.053.279.024.274-.018.547-.11.814.095-.147.198-.288.28-.445.367-.997 1.855.227 1.855.227s-1.085-.454-1.06-.24c.026.215 1.628.96 1.628.96s-1.45-.455-1.362-.114c.088.34 1.817 1.703 1.817 1.703s-1.956-1.489-2.12-1.287zm-7.268 2.65.042-.008-.06.01zM9.256 9.753c.12.13.26.234.396.343l.927-.029-1.064-.788c-.093.154-.195.303-.26.474Zm10.62 3.44c.3.215.54.373.54.373s-.24-.181-.54-.374zM7.507 8.617c-.14.229-.214.492-.215.76a3.99 3.99 0 0 0 2.358 3.64q0-.008.003-.014a3.2 3.2 0 0 1-.58-.788c-.648.099-.926-.794-.336-1.08a3.2 3.2 0 0 1 .138-1.388 3.16 3.16 0 0 1-.52-1.36q-.444.105-.848.23m1.488.82q.163-.36.402-.661a.435.435 0 0 1 .568-.557c.077-.059.166-.099.248-.15a16 16 0 0 0-1.727.284c.114.388.272.76.509 1.084m2.285 3.928c1.4 0 2.633-.723 3.344-1.816a3.4 3.4 0 0 0-1.265-.539l-.297-.023.916.9-1.197-.467.704 1.078-1.074-.832-.012.006.347 1.278-.596-1.134-.098 1.33-.401-1.326-.472 1.261.114-1.359-.015-.008-.814 1.154.286-1.067c-.34.322-.605.713-.781 1.146q.143.153.303.29.484.126 1.008.128m10.145-4.434c.971-.567 1.716-1.955 1.716-1.955s-1.893 1.955-3.205 1.665c1.186-.934 1.766-2.549 1.766-2.549s-1.506 2.325-2.448 2.423c1.086-.959 1.54-2.322 1.54-2.322s-1.237 1.817-2.196 1.944c1.287-1.161 1.338-1.893 1.338-1.893s-1.781 2.302-2.499 1.943c.858-.934 1.439-2.12 1.439-2.12s-1.489 2.019-1.893 1.69c-.277-.05.454-.958.454-.958s-.908.807-1.16.606c.454-.278 1.236-1.64 1.236-1.64S16 7.505 15.621 7.304l.731-1.483s-.73 1.483-1.715 1.23c.454-.58.63-1.112.63-1.112s-.756 1.213-1.69.885c-.22-.077.273-.635.273-.635s-.626.61-1.055.534c-.43-.076.025-.858.025-.858s-.757 1.186-.908 1.136.075-.833.075-.833-.555.908-.858.858 0-.934 0-.934-.328.984-.58.909c-.252-.076-.303-.656-.303-.656s-.068.788-.429.858c-2.725.53-5.728 1.69-9.489 5.45C3.887 10.738 5.3 7.91 11.962 7.659c5.044-.191 7.399 2.137 8.177 2.17C22.51 9.93 24 7.633 24 7.633s-1.489 1.716-2.574 1.3zm-7.74.872-.608.464v.001l.054.003a3.4 3.4 0 0 0 .554-.468m1.583-.426c0-.536-.237-.929-.594-1.217a3.2 3.2 0 0 1-.165.825.393.393 0 0 1-.328.681c-.154.233-.34.445-.549.63l.661.034-.995.237c-.025.018-.045.041-.07.058a3.2 3.2 0 0 1 1.536.691c.32-.574.504-1.235.504-1.94zM10.99 7.996a3.5 3.5 0 0 0-.785.46.43.43 0 0 1-.013.357l.885.643.023-.016-.36-1.262.627 1.12c.018-.006.04-.006.058-.011l-.02-1.251.398 1.163.477-1.15.016 1.268.012.007.713-1.005-.363 1.218.009.01 1.04-.69-.759 1.05.002.005.95-.34.041-.045a.395.395 0 0 1 .394-.632 3.4 3.4 0 0 0 .27-.784 14 14 0 0 0-2.798-.168c-.286.011-.55.033-.817.053"/></svg>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="48 -2.25 262.5 364"><defs><style>.cls-1{fill:#326ce5}.cls-3{fill:none}</style></defs><path d="M59.724 97.778a10.183 10.183 0 0 1 0-17.073l114.07-74.16a10.18 10.18 0 0 1 11.1 0l114.07 74.16a10.183 10.183 0 0 1 0 17.073l-114.07 74.16a10.18 10.18 0 0 1-11.1 0z" class="cls-1"/><path fill="#c1d2f7" d="M197.356 110.866h7.912a6.003 6.003 0 0 0 5.2-9.005l-25.924-44.902a6.004 6.004 0 0 0-10.399 0l-25.924 44.902a6.003 6.003 0 0 0 5.2 9.005h7.912a6.003 6.003 0 0 1 6.004 6.003v51.257l5.31 3.452a12.29 12.29 0 0 0 13.395 0l5.31-3.452v-51.257a6.003 6.003 0 0 1 6.004-6.003"/><path d="M173.793 353.271a10.1 10.1 0 0 0 3.457 1.402 119 119 0 0 0-6.623-3.46zm-6.456-161.195-11.316 7.356a111 111 0 0 0 11.316 6.6zm24.015 23.716c9.739 3.115 19.813 5.648 30.11 8.186 10.92 2.692 21.973 5.427 32.818 9.009l-35.416-23.025a216 216 0 0 1-27.512-9.41zm0 62.191v.586c0 2.57-2.688 4.652-6.003 4.652H173.34c-3.315 0-6.003-2.083-6.003-4.652v-6.605c-24.906-6.402-49.873-14.485-70.86-33.82l-14.529 9.445c22.468 22.05 50.28 28.933 79.715 36.189 27.178 6.698 55.18 13.651 78.685 33.442l14.68-9.544c-18.532-16.504-40.41-23.687-63.676-29.693M93.315 300.95l38.908 25.295c20.064 5.113 40.2 11.248 58.31 23.36l14.901-9.687c-19.752-13.672-42.55-19.313-66.419-25.197-15.287-3.767-30.835-7.616-45.7-13.771m74.022-82.254a124.3 124.3 0 0 1-21.534-12.62l-14.805 9.625a124.5 124.5 0 0 0 36.339 18.708zm51.41 16.294c-9.106-2.245-18.304-4.524-27.395-7.299v13.682q5.051 1.286 10.177 2.538c29.934 7.379 60.888 15.01 85.708 39.829.61.61 1.162 1.24 1.752 1.857l9.974-6.484a10.1 10.1 0 0 0 3.475-3.8 101 101 0 0 0-3.289-3.486c-22.585-22.585-50.669-29.508-80.402-36.837m-19.933 19.933q-3.722-.918-7.462-1.855v13.21c25.817 6.561 51.803 14.688 73.455 35.04l14.521-9.44-.11-.118c-22.586-22.585-50.67-29.509-80.404-36.837m-31.477-8.587c-16.28-5.278-32.118-12.397-46.344-24.131l-14.682 9.545c17.87 15.557 38.784 22.673 61.026 28.5z" class="cls-3"/><path d="M73.24 254.96c-.348-.348-.658-.71-1-1.06l-12.514 8.136a10 10 0 0 0-1.644 1.397c1.053 1.157 2.115 2.31 3.245 3.44 22.585 22.585 50.67 29.509 80.403 36.837 25.343 6.247 51.401 12.717 73.865 29.603l14.806-9.626c-20.865-16.42-45.53-22.509-71.453-28.898-29.934-7.379-60.888-15.01-85.708-39.83" class="cls-3"/><path d="m218.864 209.962-27.512-17.886v8.476a216 216 0 0 0 27.512 9.41m-27.512 5.83v11.9c9.091 2.774 18.289 5.053 27.395 7.298 29.733 7.329 57.817 14.252 80.402 36.837a101 101 0 0 1 3.29 3.486 10.193 10.193 0 0 0-3.476-13.277l-44.683-29.05c-10.845-3.58-21.898-6.316-32.818-9.008-10.297-2.538-20.371-5.071-30.11-8.186m-24.015-9.76a111 111 0 0 1-11.316-6.6l-10.218 6.644a124.3 124.3 0 0 0 21.534 12.62zm34.192 37.88q-5.125-1.264-10.177-2.539v11.695q3.737.938 7.462 1.855c29.733 7.328 57.818 14.252 80.403 36.837.039.039.073.079.111.118l9.66-6.28c-.589-.618-1.142-1.248-1.751-1.858-24.82-24.82-55.774-32.45-85.708-39.829m-34.192-9.503a124.5 124.5 0 0 1-36.34-18.708l-10.004 6.504c14.226 11.734 30.064 18.853 46.344 24.13zm0 25.842c-22.242-5.828-43.156-12.944-61.026-28.5l-9.834 6.392c20.987 19.336 45.954 27.42 70.86 33.821zm24.015 17.732c23.266 6.006 45.144 13.19 63.675 29.693l9.78-6.358c-21.652-20.352-47.638-28.479-73.455-35.04zM81.948 247.59l-9.707 6.311c.341.35.652.712 1 1.06 24.82 24.82 55.773 32.45 85.707 39.829 25.922 6.39 50.588 12.478 71.452 28.898l9.948-6.467c-23.505-19.791-51.507-26.744-78.685-33.442-29.436-7.256-57.247-14.138-79.715-36.189m-20.621 19.284c-1.13-1.13-2.192-2.283-3.245-3.44a10.154 10.154 0 0 0 1.644 15.68l33.589 21.837c14.865 6.155 30.413 10.004 45.7 13.771 23.869 5.884 46.667 11.525 66.419 25.197l10.16-6.605c-22.463-16.886-48.521-23.356-73.864-29.603-29.733-7.328-57.818-14.252-80.403-36.837m109.3 84.34a119 119 0 0 1 6.623 3.46 10.16 10.16 0 0 0 7.645-1.402l5.639-3.666c-18.11-12.112-38.247-18.247-58.311-23.36z" class="cls-1"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24"><title>PlanetScale</title><path d="M0 12C0 5.373 5.373 0 12 0c4.873 0 9.067 2.904 10.947 7.077l-15.87 15.87a12 12 0 0 1-1.935-1.099L14.99 12H12l-8.485 8.485A11.96 11.96 0 0 1 0 12m12.004 12L24 12.004C23.998 18.628 18.628 23.998 12.004 24"/></svg>
|
||||
|
Before Width: | Height: | Size: 326 B |
@@ -3,7 +3,7 @@ import { Button, Popover, Tooltip } from 'antd';
|
||||
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { useExportRawData } from 'hooks/useDownloadOptionsMenu/useDownloadOptionsMenu';
|
||||
import { useExportRawData } from 'hooks/useExportData/useServerExport';
|
||||
import { Download, LoaderCircle } from '@signozhq/icons';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ type BadgeColor =
|
||||
|
||||
interface HttpStatusBadgeProps {
|
||||
statusCode: string | number;
|
||||
testId?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function getStatusCodeColor(statusCode: number): BadgeColor {
|
||||
@@ -37,8 +35,6 @@ function getStatusCodeColor(statusCode: number): BadgeColor {
|
||||
|
||||
function HttpStatusBadge({
|
||||
statusCode,
|
||||
testId,
|
||||
className,
|
||||
}: HttpStatusBadgeProps): JSX.Element | null {
|
||||
const numericStatusCode = Number(statusCode);
|
||||
|
||||
@@ -49,12 +45,7 @@ function HttpStatusBadge({
|
||||
const color = getStatusCodeColor(numericStatusCode);
|
||||
|
||||
return (
|
||||
<Badge
|
||||
color={color}
|
||||
variant="outline"
|
||||
data-testid={testId}
|
||||
className={className}
|
||||
>
|
||||
<Badge color={color} variant="outline">
|
||||
{statusCode}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
padding: 0px 8px;
|
||||
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
|
||||
background: var(--input-with-label-background-color, var(--l2-background));
|
||||
color: var(--input-with-label-color, var(--l2-foreground));
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
color: var(--l2-foreground);
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
@@ -35,54 +35,21 @@
|
||||
min-width: 150px;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
|
||||
--input-border-radius: 0px;
|
||||
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
|
||||
background: var(--input-with-label-background-color, var(--l2-background));
|
||||
color: var(--input-with-label-color, var(--l2-foreground));
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
font-size: 12px !important;
|
||||
line-height: 25px;
|
||||
|
||||
position: relative;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ant-select-selector {
|
||||
position: relative;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.ant-select:hover .ant-select-selector,
|
||||
.ant-select-focused .ant-select-selector {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.input__has-label-after {
|
||||
margin-left: -1px;
|
||||
|
||||
.ant-select-selector {
|
||||
margin-left: -1px;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.input__has-close-button {
|
||||
.ant-select-selector {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
~ .close-btn {
|
||||
margin-left: -1px;
|
||||
}
|
||||
}
|
||||
line-height: 27px;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--input-with-label-color, var(--l3-foreground)) !important;
|
||||
color: var(--l2-foreground) !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
&[type='number']::-webkit-inner-spin-button,
|
||||
@@ -96,35 +63,25 @@
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
|
||||
background: var(--input-with-label-background-color, var(--l2-background));
|
||||
height: 100%;
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
position: relative;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&.labelAfter {
|
||||
.input {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
|
||||
background: var(--input-with-label-background-color, var(--l2-background));
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
|
||||
.ant-select-selector {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
border-left: none;
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +45,7 @@ function InputWithLabel({
|
||||
>
|
||||
{!labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
|
||||
<Input
|
||||
className={cx('input', {
|
||||
'input__has-label-after': !labelAfter,
|
||||
'input__has-close-button': !!onClose,
|
||||
})}
|
||||
className="input"
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
value={inputValue}
|
||||
|
||||
@@ -80,8 +80,8 @@
|
||||
width: 1px;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
var(--query-builder-v2-border-color, var(--l2-border)),
|
||||
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
|
||||
var(--l1-border),
|
||||
var(--l1-border) 4px,
|
||||
transparent 4px,
|
||||
transparent 8px
|
||||
);
|
||||
@@ -101,8 +101,7 @@
|
||||
top: 12px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-left: 6px dotted
|
||||
var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border-left: 6px dotted var(--l1-border);
|
||||
}
|
||||
|
||||
/* Horizontal line pointing from vertical to the item */
|
||||
@@ -115,8 +114,8 @@
|
||||
height: 1px;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
var(--query-builder-v2-border-color, var(--l2-border)),
|
||||
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
|
||||
var(--l1-border),
|
||||
var(--l1-border) 4px,
|
||||
transparent 4px,
|
||||
transparent 8px
|
||||
);
|
||||
@@ -242,8 +241,7 @@
|
||||
top: 12px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-left: 6px dotted
|
||||
var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border-left: 6px dotted var(--l1-border);
|
||||
}
|
||||
|
||||
/* Horizontal line pointing from vertical to the item */
|
||||
@@ -256,8 +254,8 @@
|
||||
height: 1px;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
var(--query-builder-v2-border-color, var(--l2-border)),
|
||||
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
|
||||
var(--l1-border),
|
||||
var(--l1-border) 4px,
|
||||
transparent 4px,
|
||||
transparent 8px
|
||||
);
|
||||
@@ -275,16 +273,6 @@
|
||||
line-height: 16px; /* 128.571% */
|
||||
|
||||
resize: none;
|
||||
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
);
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
|
||||
&:placeholder {
|
||||
color: var(--query-builder-v2-placeholder-color, var(--l3-foreground));
|
||||
}
|
||||
}
|
||||
|
||||
.formula-legend {
|
||||
@@ -294,42 +282,15 @@
|
||||
.ant-input-group-addon {
|
||||
border-top-left-radius: 0px !important;
|
||||
border-top-right-radius: 0px !important;
|
||||
background: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
);
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground));
|
||||
background: var(--l2-background);
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ant-input {
|
||||
border-top-left-radius: 0px !important;
|
||||
border-top-right-radius: 0px !important;
|
||||
|
||||
height: 36px;
|
||||
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
);
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
|
||||
position: relative;
|
||||
margin-left: -1px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
z-index: 1;
|
||||
border-color: var(--internal-ant-border-color-hover);
|
||||
}
|
||||
|
||||
&:placeholder {
|
||||
color: var(--query-builder-v2-placeholder-color, var(--l3-foreground));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,8 +323,8 @@
|
||||
width: 1px;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
var(--query-builder-v2-border-color, var(--l2-border)),
|
||||
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
|
||||
var(--l1-border),
|
||||
var(--l1-border) 4px,
|
||||
transparent 4px,
|
||||
transparent 8px
|
||||
);
|
||||
@@ -434,8 +395,8 @@
|
||||
width: 1px;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
var(--query-builder-v2-border-color, var(--l2-border)),
|
||||
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
|
||||
var(--l1-border),
|
||||
var(--l1-border) 4px,
|
||||
transparent 4px,
|
||||
transparent 8px
|
||||
);
|
||||
@@ -451,7 +412,7 @@
|
||||
min-width: 120px;
|
||||
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l1-background);
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
@@ -496,16 +457,13 @@
|
||||
|
||||
.ant-select-selector {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border)) !important;
|
||||
background: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
height: 36px !important;
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
height: 34px !important;
|
||||
box-sizing: border-box !important;
|
||||
|
||||
.ant-select-selection-item {
|
||||
color: var(--query-builder-v2-color, var(--l1-foreground));
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,11 +92,6 @@
|
||||
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
|
||||
.ant-select-selector {
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.ant-select-selection-search-input {
|
||||
min-width: max-content !important;
|
||||
max-width: 100% !important;
|
||||
@@ -105,12 +100,9 @@
|
||||
|
||||
.ant-select-selector {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border)) !important;
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground));
|
||||
border: 1.005px solid var(--l1-border);
|
||||
background: var(--l1-background);
|
||||
color: var(--l1-foreground);
|
||||
font-family: 'Geist Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
@@ -131,7 +123,6 @@
|
||||
.input {
|
||||
flex: initial;
|
||||
width: 100px !important;
|
||||
min-height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
.ant-select-selection-search-input {
|
||||
font-size: 12px !important;
|
||||
line-height: 25px;
|
||||
line-height: 27px;
|
||||
&::placeholder {
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
|
||||
color: var(--l2-foreground) !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,9 @@
|
||||
.ant-select-selector {
|
||||
width: 100%;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border)) !important;
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground));
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background);
|
||||
color: var(--l1-foreground);
|
||||
font-family: 'Geist Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
@@ -36,49 +33,36 @@
|
||||
min-height: 36px;
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
color: var(
|
||||
--query-builder-v2-placeholder-color,
|
||||
var(--l3-foreground)
|
||||
) !important;
|
||||
color: var(--l2-foreground) !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qb-select-popover.ant-select-dropdown {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
background: var(--query-builder-v2-background-color, var(--l2-background));
|
||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
.ant-select-dropdown {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l1-background);
|
||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
.ant-select-item {
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground));
|
||||
font-family: 'Geist Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
.ant-select-item {
|
||||
color: var(--l1-foreground);
|
||||
font-family: 'Geist Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
&:hover,
|
||||
&.ant-select-item-option-active {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.ant-select-item-option-active {
|
||||
background: var(
|
||||
--query-builder-v2-selected-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
}
|
||||
|
||||
&.ant-select-item-option-selected {
|
||||
background: var(
|
||||
--query-builder-v2-selected-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
font-weight: 600;
|
||||
&.ant-select-item-option-selected {
|
||||
background: var(--l3-background) !important;
|
||||
border: 1px solid var(--l1-border);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,6 @@ export const MetricsSelect = memo(function MetricsSelect({
|
||||
{signalSourceChangeEnabled && (
|
||||
<Select
|
||||
className="source-selector"
|
||||
popupClassName="qb-select-popover"
|
||||
placeholder="Source"
|
||||
options={SOURCE_OPTIONS}
|
||||
value={source}
|
||||
|
||||
@@ -1,23 +1,8 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.query-add-ons {
|
||||
width: 100%;
|
||||
|
||||
--toggle-group-secondary-bg: var(
|
||||
--query-builder-v2-toggle-group-background-color,
|
||||
var(--l1-background-hover)
|
||||
);
|
||||
--toggle-group-secondary-border: var(
|
||||
--query-builder-v2-toggle-group-border-color,
|
||||
var(--l2-border)
|
||||
);
|
||||
--toggle-group-secondary-active-bg: var(
|
||||
--query-builder-v2-toggle-group-active-background-color,
|
||||
var(--l1-background)
|
||||
);
|
||||
--toggle-group-secondary-bg-hover: var(
|
||||
--query-builder-v2-toggle-group-background-color-hover,
|
||||
var(--l2-background)
|
||||
);
|
||||
|
||||
.add-on-tab-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -44,33 +29,32 @@
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground));
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
> button {
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border: 1px solid var(--l1-border);
|
||||
border-left: none;
|
||||
min-width: 120px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
|
||||
&:first-child {
|
||||
border-left: 1px solid
|
||||
var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border-left: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: var(--query-builder-v2-border-color, var(--l2-border));
|
||||
background: var(--l1-border);
|
||||
}
|
||||
|
||||
&[data-state='on'] {
|
||||
color: var(--text-robin-500);
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border: 1px solid var(--l1-border);
|
||||
|
||||
display: none;
|
||||
|
||||
&::before {
|
||||
background: var(--query-builder-v2-border-color, var(--l2-border));
|
||||
background: var(--l1-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +65,7 @@
|
||||
height: 30px;
|
||||
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@@ -94,13 +78,10 @@
|
||||
align-items: center;
|
||||
|
||||
.having-filter-select-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
background: var(--query-builder-v2-background-color, var(--l2-background));
|
||||
padding-right: 38px;
|
||||
align-items: center;
|
||||
|
||||
.having-filter-select-editor {
|
||||
border-radius: 2px;
|
||||
@@ -125,17 +106,15 @@
|
||||
}
|
||||
|
||||
.cm-content {
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border-left-width: 0px;
|
||||
border-right-width: 0px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
padding: 0px !important;
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border-color: var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,32 +218,17 @@
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
min-height: 34px;
|
||||
line-height: 32px !important;
|
||||
line-height: 36px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
|
||||
&,
|
||||
.ͼ1a {
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground));
|
||||
}
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
@@ -273,11 +237,8 @@
|
||||
}
|
||||
|
||||
.chip-decorator {
|
||||
background: var(
|
||||
--query-builder-v2-chip-decorator-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
|
||||
background: var(--l3-background) !important;
|
||||
color: var(--l1-foreground) !important;
|
||||
border-radius: 4px;
|
||||
padding: 2px 4px;
|
||||
margin-right: 4px;
|
||||
@@ -285,38 +246,34 @@
|
||||
}
|
||||
|
||||
.cm-selectionBackground {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.cm-activeLine > span {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.cm-placeholder {
|
||||
color: var(
|
||||
--query-builder-v2-placeholder-color,
|
||||
var(--l3-foreground)
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
background: var(--query-builder-v2-background-color, var(--l2-background));
|
||||
height: 100%;
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
|
||||
border-left: transparent;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
|
||||
&:focus:not(:focus-visible),
|
||||
&.ant-btn:focus:not(:focus-visible) {
|
||||
border-color: var(--l2-border);
|
||||
border-left-color: transparent;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,8 +300,20 @@
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
input {
|
||||
min-height: 36px;
|
||||
$add-on-row-height: 38px;
|
||||
|
||||
.periscope-input-with-label {
|
||||
.input {
|
||||
.ant-select {
|
||||
height: $add-on-row-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-with-label {
|
||||
.input {
|
||||
height: $add-on-row-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 12px;
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
|
||||
color: var(--l2-foreground) !important;
|
||||
|
||||
&.error {
|
||||
.cm-editor {
|
||||
@@ -51,15 +51,14 @@
|
||||
|
||||
.cm-content {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border: 1px solid var(--l1-border);
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
padding: 0px !important;
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border-color: var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +74,7 @@
|
||||
right: 0px !important;
|
||||
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border: 1px solid var(--l1-border);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
color-mix(in srgb, var(--card) 80%, transparent) 0%,
|
||||
@@ -119,7 +118,7 @@
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
|
||||
color: var(--l2-foreground) !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
|
||||
.cm-completionIcon {
|
||||
@@ -128,10 +127,7 @@
|
||||
|
||||
&:hover,
|
||||
&[aria-selected='true'] {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
color: var(--l1-foreground) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
@@ -146,24 +142,15 @@
|
||||
.cm-line {
|
||||
line-height: 36px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
@@ -172,11 +159,8 @@
|
||||
}
|
||||
|
||||
.chip-decorator {
|
||||
background: var(
|
||||
--query-builder-v2-chip-decorator-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
color: var(--query-builder-v2-color, var(--l1-foreground)) !important;
|
||||
background: var(--l3-background) !important;
|
||||
color: var(--l1-foreground) !important;
|
||||
border-radius: 4px;
|
||||
padding: 2px 4px;
|
||||
margin-right: 4px;
|
||||
@@ -184,10 +168,7 @@
|
||||
}
|
||||
|
||||
.cm-selectionBackground {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
}
|
||||
@@ -220,11 +201,12 @@
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
background: var(--query-builder-v2-background-color, var(--l2-background));
|
||||
height: 100%;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l1-background);
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
|
||||
border-left: transparent;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
}
|
||||
@@ -235,13 +217,13 @@
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border: 1px solid var(--l1-border);
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground));
|
||||
color: var(--l1-foreground);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
@@ -256,10 +238,9 @@
|
||||
.query-aggregation-interval-input-container {
|
||||
.query-aggregation-interval-input {
|
||||
input {
|
||||
min-height: 36px;
|
||||
max-width: 120px;
|
||||
&::placeholder {
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground));
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,8 +251,8 @@
|
||||
|
||||
.query-aggregation-error-popover {
|
||||
.ant-popover-inner {
|
||||
background-color: var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
background-color: var(--l1-border);
|
||||
border: 1px solid var(--l1-border);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.add-trace-operator-button,
|
||||
.add-new-query-button,
|
||||
.add-formula-button {
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
background: var(--query-builder-v2-background-color, var(--l2-background));
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@@ -40,14 +40,11 @@ $max-recents-shown: 5;
|
||||
.query-status-container {
|
||||
width: 32px;
|
||||
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border: 1px solid var(--l1-border);
|
||||
border-radius: 2px;
|
||||
border-top-left-radius: 0px !important;
|
||||
border-bottom-left-radius: 0px !important;
|
||||
@@ -86,16 +83,16 @@ $max-recents-shown: 5;
|
||||
|
||||
.cm-content {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border: 1px solid var(--l1-border);
|
||||
padding: 0px !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--query-builder-v2-border-color, var(--l2-border));
|
||||
border-color: var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
&.cm-focused {
|
||||
outline: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
|
||||
outline: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete {
|
||||
@@ -186,17 +183,11 @@ $max-recents-shown: 5;
|
||||
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
color: var(--l2-foreground) !important;
|
||||
|
||||
&:hover {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
.cm-completionIcon {
|
||||
@@ -214,10 +205,7 @@ $max-recents-shown: 5;
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
@@ -286,49 +274,25 @@ $max-recents-shown: 5;
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
line-height: 36px !important;
|
||||
line-height: 34px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(
|
||||
--query-builder-v2-background-color,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
|
||||
&,
|
||||
.ͼ1a {
|
||||
color: var(--query-builder-v2-color, var(--l2-foreground));
|
||||
}
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-selectionBackground {
|
||||
background: var(
|
||||
--query-builder-v2-selection-background-color,
|
||||
var(--l3-background)
|
||||
) !important;
|
||||
background: var(--l3-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.cm-placeholder {
|
||||
color: var(
|
||||
--query-builder-v2-placeholder-color,
|
||||
var(--l3-foreground)
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cursor-position {
|
||||
|
||||
@@ -65,14 +65,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
// Meant to fix the query builder colors
|
||||
--input-background: var(--l2-background);
|
||||
--input-hover-background: var(--l2-background);
|
||||
--input-focus-background: var(--l2-background);
|
||||
--input-border-color: var(--l2-border);
|
||||
--input-hover-border-color: var(--internal-ant-border-color-hover);
|
||||
--input-focus-border-color: var(--internal-ant-border-color-hover);
|
||||
}
|
||||
|
||||
&-aggregation-container {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Button } from 'antd';
|
||||
import { Checkbox } from '@signozhq/ui/checkbox';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
|
||||
interface CheckboxValueRowProps {
|
||||
value: string;
|
||||
@@ -47,11 +46,9 @@ function CheckboxValueRow({
|
||||
{customRendererForValue ? (
|
||||
customRendererForValue(value)
|
||||
) : (
|
||||
<TooltipSimple title={String(value)} side="top" align="start">
|
||||
<Typography.Text className="value-string" truncate={1}>
|
||||
{String(value)}
|
||||
</Typography.Text>
|
||||
</TooltipSimple>
|
||||
<Typography.Text className="value-string" truncate={1}>
|
||||
{String(value)}
|
||||
</Typography.Text>
|
||||
)}
|
||||
<Button type="text" className="only-btn">
|
||||
{onlyButtonLabel}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { TabsProps } from 'antd';
|
||||
import { History } from 'history';
|
||||
|
||||
export type TabRoutes = {
|
||||
name: React.ReactNode;
|
||||
route: string;
|
||||
Component: ComponentType;
|
||||
Component: () => JSX.Element;
|
||||
key: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { type MouseEvent, type ReactNode } from 'react';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
|
||||
interface TagBadgeProps {
|
||||
children: ReactNode;
|
||||
// Show a remove button (editable contexts: create modal, settings drawer).
|
||||
closable?: boolean;
|
||||
onClose?: (event: MouseEvent<HTMLButtonElement>) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// The single sienna tag chip used everywhere dashboards render tags — list rows,
|
||||
// the details header, and the tag editors. Kept as one component so the tag
|
||||
// styling stays identical across all of them.
|
||||
function TagBadge({
|
||||
children,
|
||||
closable,
|
||||
onClose,
|
||||
className,
|
||||
}: TagBadgeProps): JSX.Element {
|
||||
return (
|
||||
<Badge
|
||||
color="sienna"
|
||||
variant="outline"
|
||||
className={className}
|
||||
closable={closable}
|
||||
onClose={onClose}
|
||||
>
|
||||
{children}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
export default TagBadge;
|
||||
@@ -15,28 +15,59 @@
|
||||
border: 1px solid var(--l2-border);
|
||||
}
|
||||
|
||||
// Sienna chip via the @signozhq Badge; this only constrains its width.
|
||||
// Sienna chip — matches the dashboard list-row tag badge.
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
max-width: 240px;
|
||||
height: 24px;
|
||||
padding: 2px 4px 2px 8px;
|
||||
border-radius: 50px;
|
||||
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);
|
||||
font-size: 13px;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 20px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
// The tag label is a bare, chrome-less button inside the Badge (double-click to
|
||||
// edit); strip its Button styling and let it ellipsize.
|
||||
.tagLabel {
|
||||
--button-height: auto;
|
||||
--button-padding: 0;
|
||||
--button-gap: 0;
|
||||
--button-variant-ghost-background-color: transparent;
|
||||
--button-variant-ghost-hover-background-color: transparent;
|
||||
--button-variant-ghost-color: currentColor;
|
||||
--button-variant-ghost-hover-color: currentColor;
|
||||
--button-variant-ghost-color: inherit;
|
||||
--button-variant-ghost-hover-color: inherit;
|
||||
overflow: hidden;
|
||||
max-width: 200px;
|
||||
font-size: 13px;
|
||||
font-weight: var(--font-weight-normal);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.remove {
|
||||
// Size overrides to fit the chip, plus a sienna-tinted hover — the Button's
|
||||
// default ghost hover is a grey that clashes with the chip. Resting color is
|
||||
// left at the Button default.
|
||||
--button-height: 16px;
|
||||
--button-padding: 0;
|
||||
--button-border-radius: 50%;
|
||||
--button-variant-ghost-hover-background-color: color-mix(
|
||||
in srgb,
|
||||
var(--bg-sienna-500) 22%,
|
||||
transparent
|
||||
);
|
||||
--button-variant-ghost-hover-color: var(--bg-sienna-400);
|
||||
width: 16px;
|
||||
min-width: 16px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
|
||||
@@ -2,9 +2,9 @@ import { type ChangeEvent, type KeyboardEvent, useState } from 'react';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { X } from '@signozhq/icons';
|
||||
import cx from 'classnames';
|
||||
|
||||
import TagBadge from '../TagBadge/TagBadge';
|
||||
import { parseKeyValueTag } from './utils';
|
||||
|
||||
import styles from './TagKeyValueInput.module.scss';
|
||||
@@ -65,9 +65,7 @@ function TagKeyValueInput({
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
|
||||
// Plain Enter adds the tag; let Cmd/Ctrl+Enter pass through so a host form
|
||||
// (e.g. a modal) can submit on it.
|
||||
if (e.key === 'Enter' && !e.metaKey && !e.ctrlKey) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
commit();
|
||||
}
|
||||
@@ -95,17 +93,11 @@ function TagKeyValueInput({
|
||||
};
|
||||
|
||||
const handleEditKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
|
||||
// Plain Enter commits the edit; let Cmd/Ctrl+Enter pass through so a host
|
||||
// form (e.g. a modal) can submit on it.
|
||||
if (e.key === 'Enter' && !e.metaKey && !e.ctrlKey) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
commitEdit();
|
||||
} else if (e.key === 'Escape') {
|
||||
// Contain Escape so it cancels the inline edit instead of bubbling up and
|
||||
// closing the host drawer/modal.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
cancelEdit();
|
||||
}
|
||||
};
|
||||
@@ -128,23 +120,27 @@ function TagKeyValueInput({
|
||||
onBlur={commitEdit}
|
||||
/>
|
||||
) : (
|
||||
<TagBadge
|
||||
key={tag}
|
||||
className={styles.tag}
|
||||
closable
|
||||
onClose={(): void => removeTag(tag)}
|
||||
>
|
||||
<div key={tag} className={styles.tag} data-testid={`${testId}-chip`}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
className={styles.tagLabel}
|
||||
title="Double-click to edit"
|
||||
testId={`${testId}-chip`}
|
||||
onDoubleClick={(): void => startEdit(index)}
|
||||
>
|
||||
{tag}
|
||||
</Button>
|
||||
</TagBadge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
size="icon"
|
||||
className={styles.remove}
|
||||
aria-label={`Remove ${tag}`}
|
||||
onClick={(): void => removeTag(tag)}
|
||||
>
|
||||
<X size={12} />
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
<Input
|
||||
|
||||
@@ -57,13 +57,10 @@ function TimelineV3(props: ITimelineV3Props): JSX.Element {
|
||||
}
|
||||
|
||||
const timeAtCursor = offsetTimestamp + cursorXPercent * spread;
|
||||
// Use the same width-derived interval spread the ticks use, so the badge
|
||||
// unit always matches the tick unit (narrow rulers pick fewer intervals).
|
||||
const intervalSpread = spread / getMinimumIntervalsBasedOnWidth(width);
|
||||
const unit = getIntervalUnit(intervalSpread, offsetTimestamp);
|
||||
const unit = getIntervalUnit(spread, offsetTimestamp);
|
||||
const formatted = toFixed(resolveTimeFromInterval(timeAtCursor, unit), 2);
|
||||
return `${formatted}${unit.name}`;
|
||||
}, [cursorXPercent, spread, offsetTimestamp, width]);
|
||||
}, [cursorXPercent, spread, offsetTimestamp]);
|
||||
|
||||
if (endTimestamp < startTimestamp) {
|
||||
console.error(
|
||||
@@ -97,17 +94,12 @@ function TimelineV3(props: ITimelineV3Props): JSX.Element {
|
||||
>
|
||||
<text
|
||||
x={index === intervals.length - 1 ? -10 : 0}
|
||||
y={timelineHeight * 2 - 3}
|
||||
y={timelineHeight * 2}
|
||||
fill={strokeColor}
|
||||
>
|
||||
{interval.label}
|
||||
</text>
|
||||
<line
|
||||
y1={0}
|
||||
y2={timelineHeight - 3}
|
||||
stroke={strokeColor}
|
||||
strokeWidth="1"
|
||||
/>
|
||||
<line y1={0} y2={timelineHeight} stroke={strokeColor} strokeWidth="1" />
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import {
|
||||
getIntervals,
|
||||
getIntervalUnit,
|
||||
getMinimumIntervalsBasedOnWidth,
|
||||
} from '../utils';
|
||||
|
||||
// A tick label looks like "200.00ms" / "1.10s" — grab the trailing unit name.
|
||||
function unitOfLabel(label: string): string {
|
||||
const match = label.match(/[a-z]+$/i);
|
||||
return match ? match[0] : '';
|
||||
}
|
||||
|
||||
describe('getMinimumIntervalsBasedOnWidth', () => {
|
||||
it('returns fewer intervals for narrower rulers', () => {
|
||||
expect(getMinimumIntervalsBasedOnWidth(500)).toBe(3);
|
||||
expect(getMinimumIntervalsBasedOnWidth(700)).toBe(4);
|
||||
expect(getMinimumIntervalsBasedOnWidth(900)).toBe(5);
|
||||
expect(getMinimumIntervalsBasedOnWidth(1200)).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIntervalUnit', () => {
|
||||
it('selects the unit from the interval spread', () => {
|
||||
expect(getIntervalUnit(130, 0).name).toBe('ms');
|
||||
expect(getIntervalUnit(1100, 0).name).toBe('s');
|
||||
expect(getIntervalUnit(70_000, 0).name).toBe('m');
|
||||
});
|
||||
|
||||
it('accounts for a large offset (deep-zoom labels stay readable)', () => {
|
||||
// Cursor spread is tiny but the window starts 5,000,000ms into the trace.
|
||||
expect(getIntervalUnit(100, 5_000_000).name).toBe('hr');
|
||||
});
|
||||
|
||||
// Regression: the interval COUNT changes the chosen unit, so the crosshair
|
||||
// badge must use the same width-derived count as the ticks. On a 5.5s trace a
|
||||
// narrow ruler (5 intervals → 1100ms → "s") and a wide one (6 intervals →
|
||||
// 916ms → "ms") pick different units.
|
||||
it('can resolve to different units for the same spread at different counts', () => {
|
||||
const spread = 5500;
|
||||
expect(getIntervalUnit(spread / 5, 0).name).toBe('s');
|
||||
expect(getIntervalUnit(spread / 6, 0).name).toBe('ms');
|
||||
});
|
||||
});
|
||||
|
||||
describe('badge/tick unit consistency', () => {
|
||||
// The invariant the fix guarantees: when the badge and the ticks are fed the
|
||||
// same width-derived intervalSpread, every tick label uses the badge's unit.
|
||||
it.each([
|
||||
{ spread: 1287, width: 900 },
|
||||
{ spread: 5500, width: 900 }, // narrow → 5 intervals, the mismatch case
|
||||
{ spread: 5500, width: 1200 }, // wide → 6 intervals
|
||||
{ spread: 120_000, width: 700 },
|
||||
])('spread=$spread width=$width', ({ spread, width }) => {
|
||||
const minIntervals = getMinimumIntervalsBasedOnWidth(width);
|
||||
const intervalSpread = spread / minIntervals;
|
||||
const badgeUnit = getIntervalUnit(intervalSpread, 0).name;
|
||||
const intervals = getIntervals(intervalSpread, spread, 0);
|
||||
|
||||
intervals.forEach((interval) => {
|
||||
expect(unitOfLabel(interval.label)).toBe(badgeUnit);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,18 +10,14 @@ export type { Interval };
|
||||
|
||||
/**
|
||||
* Select the interval unit matching the timeline's logic.
|
||||
* Exported so crosshair labels use the same unit as the timeline ticks.
|
||||
*
|
||||
* Takes the already-computed `intervalSpread` (spread / minIntervals) rather
|
||||
* than deriving it from a hardcoded interval count — the tick count is
|
||||
* width-dependent (`getMinimumIntervalsBasedOnWidth`), so callers must pass the
|
||||
* same `intervalSpread` the ticks use or the badge unit can diverge from the
|
||||
* ticks (e.g. ms vs s) on narrower rulers like the waterfall.
|
||||
* Exported so crosshair labels use the same unit as timeline ticks.
|
||||
*/
|
||||
export function getIntervalUnit(
|
||||
intervalSpread: number,
|
||||
spread: number,
|
||||
offsetTimestamp: number,
|
||||
): IIntervalUnit {
|
||||
const minIntervals = 6;
|
||||
const intervalSpread = spread / minIntervals;
|
||||
const valueForUnitSelection = Math.max(offsetTimestamp, intervalSpread);
|
||||
let unit: IIntervalUnit = INTERVAL_UNITS[0];
|
||||
for (let idx = INTERVAL_UNITS.length - 1; idx >= 0; idx -= 1) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* Overlay stays below content */
|
||||
[data-slot='dialog-overlay'] {
|
||||
z-index: 1000 !important;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* Dialog content always above overlay */
|
||||
[data-slot='dialog-content'] {
|
||||
position: fixed;
|
||||
z-index: 1001 !important;
|
||||
z-index: 60;
|
||||
background: var(--l1-background);
|
||||
color: var(--l1-foreground);
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
} from 'container/AIAssistant/store/useAIAssistantStore';
|
||||
import { useThemeMode } from 'hooks/useDarkMode';
|
||||
import { useIsAIAssistantEnabled } from 'hooks/useIsAIAssistantEnabled';
|
||||
import { IS_DEV } from 'lib/env';
|
||||
import history from 'lib/history';
|
||||
import { ROLES as UserRole } from 'types/roles';
|
||||
|
||||
@@ -31,33 +30,6 @@ import { useCmdK } from '../../providers/cmdKProvider';
|
||||
|
||||
import './cmdKPalette.scss';
|
||||
|
||||
const AuthZDevModal = IS_DEV
|
||||
? React.lazy(() =>
|
||||
import('lib/authz/devtools/AuthZDevModal/AuthZDevModal').then((m) => ({
|
||||
default: m.AuthZDevModal,
|
||||
})),
|
||||
)
|
||||
: null;
|
||||
|
||||
const AuthZDevFloatingIndicator = IS_DEV
|
||||
? React.lazy(() =>
|
||||
import('lib/authz/devtools/AuthZDevFloatingIndicator/AuthZDevFloatingIndicator').then(
|
||||
(m) => ({
|
||||
default: m.AuthZDevFloatingIndicator,
|
||||
}),
|
||||
),
|
||||
)
|
||||
: null;
|
||||
|
||||
const openAuthZDevModal = IS_DEV
|
||||
? (): void => {
|
||||
void import('lib/authz/devtools/useAuthZDevStore').then((m) => {
|
||||
m.openAuthZDevModal();
|
||||
return m;
|
||||
});
|
||||
}
|
||||
: undefined;
|
||||
|
||||
type CmdAction = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -138,7 +110,6 @@ export function CmdKPalette({
|
||||
aiAssistant: isAIAssistantEnabled
|
||||
? { open: handleOpenAIAssistant }
|
||||
: undefined,
|
||||
authzDevTools: openAuthZDevModal ? { open: openAuthZDevModal } : undefined,
|
||||
});
|
||||
|
||||
// RBAC filter: show action if no roles set OR current user role is included
|
||||
@@ -175,57 +146,37 @@ export function CmdKPalette({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CommandDialog
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
position="top"
|
||||
offset={110}
|
||||
>
|
||||
<CommandInput placeholder="Search…" className="cmdk-input-wrapper" />
|
||||
<CommandList className="cmdk-list-scroll">
|
||||
<CommandEmpty>No results</CommandEmpty>
|
||||
{grouped.map(([section, items]) => (
|
||||
<CommandGroup
|
||||
key={section}
|
||||
heading={section}
|
||||
className="cmdk-section-heading"
|
||||
>
|
||||
{items.map((it) => (
|
||||
<CommandItem
|
||||
key={it.id}
|
||||
onSelect={(): void => handleInvoke(it)}
|
||||
value={it.name}
|
||||
className={theme === 'light' ? 'cmdk-item-light' : 'cmdk-item'}
|
||||
<CommandDialog open={open} onOpenChange={setOpen} position="top" offset={110}>
|
||||
<CommandInput placeholder="Search…" className="cmdk-input-wrapper" />
|
||||
<CommandList className="cmdk-list-scroll">
|
||||
<CommandEmpty>No results</CommandEmpty>
|
||||
{grouped.map(([section, items]) => (
|
||||
<CommandGroup
|
||||
key={section}
|
||||
heading={section}
|
||||
className="cmdk-section-heading"
|
||||
>
|
||||
{items.map((it) => (
|
||||
<CommandItem
|
||||
key={it.id}
|
||||
onSelect={(): void => handleInvoke(it)}
|
||||
value={it.name}
|
||||
className={theme === 'light' ? 'cmdk-item-light' : 'cmdk-item'}
|
||||
>
|
||||
<span
|
||||
className={cx('cmd-item-icon', it.id === 'ai-assistant' && 'noz-icon')}
|
||||
>
|
||||
<span
|
||||
className={cx(
|
||||
'cmd-item-icon',
|
||||
it.id === 'ai-assistant' && 'noz-icon',
|
||||
)}
|
||||
>
|
||||
{it.icon}
|
||||
</span>
|
||||
{it.name}
|
||||
{it.shortcut && it.shortcut.length > 0 && (
|
||||
<CommandShortcut>{it.shortcut.join(' • ')}</CommandShortcut>
|
||||
)}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
{IS_DEV && AuthZDevModal && (
|
||||
<React.Suspense fallback={null}>
|
||||
<AuthZDevModal />
|
||||
</React.Suspense>
|
||||
)}
|
||||
{IS_DEV && AuthZDevFloatingIndicator && (
|
||||
<React.Suspense fallback={null}>
|
||||
<AuthZDevFloatingIndicator />
|
||||
</React.Suspense>
|
||||
)}
|
||||
</>
|
||||
{it.icon}
|
||||
</span>
|
||||
{it.name}
|
||||
{it.shortcut && it.shortcut.length > 0 && (
|
||||
<CommandShortcut>{it.shortcut.join(' • ')}</CommandShortcut>
|
||||
)}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ export enum FeatureKeys {
|
||||
USE_JSON_BODY = 'use_json_body',
|
||||
USE_FINE_GRAINED_AUTHZ = 'use_fine_grained_authz',
|
||||
USE_DASHBOARD_V2 = 'use_dashboard_v2',
|
||||
USE_INFRA_MONITORING_V2 = 'use_infra_monitoring_v2',
|
||||
ENABLE_AI_OBSERVABILITY = 'enable_ai_observability',
|
||||
ENABLE_METRICS_REDUCTION = 'enable_metrics_reduction',
|
||||
}
|
||||
|
||||
@@ -31,12 +31,6 @@ export enum LOCALSTORAGE {
|
||||
METRICS_LIST_OPTIONS = 'METRICS_LIST_OPTIONS',
|
||||
SHOW_EXCEPTIONS_QUICK_FILTERS = 'SHOW_EXCEPTIONS_QUICK_FILTERS',
|
||||
QUICK_FILTERS_SETTINGS_ANNOUNCEMENT = 'QUICK_FILTERS_SETTINGS_ANNOUNCEMENT',
|
||||
QUICK_FILTERS_WIDTH_LOGS = 'QUICK_FILTERS_WIDTH_LOGS',
|
||||
QUICK_FILTERS_WIDTH_TRACES = 'QUICK_FILTERS_WIDTH_TRACES',
|
||||
QUICK_FILTERS_WIDTH_METER = 'QUICK_FILTERS_WIDTH_METER',
|
||||
QUICK_FILTERS_WIDTH_API_MONITORING = 'QUICK_FILTERS_WIDTH_API_MONITORING',
|
||||
QUICK_FILTERS_WIDTH_EXCEPTIONS = 'QUICK_FILTERS_WIDTH_EXCEPTIONS',
|
||||
QUICK_FILTERS_WIDTH_INFRA = 'QUICK_FILTERS_WIDTH_INFRA',
|
||||
FUNNEL_STEPS = 'FUNNEL_STEPS',
|
||||
SPAN_DETAILS_PINNED_ATTRIBUTES = 'SPAN_DETAILS_PINNED_ATTRIBUTES',
|
||||
LAST_USED_CUSTOM_TIME_RANGES = 'LAST_USED_CUSTOM_TIME_RANGES',
|
||||
|
||||
@@ -89,10 +89,8 @@ const ROUTES = {
|
||||
AI_ASSISTANT_BASE: '/ai-assistant',
|
||||
AI_ASSISTANT_ICON_PREVIEW: '/ai-assistant-icon-preview',
|
||||
MCP_SERVER: '/settings/mcp-server',
|
||||
LLM_OBSERVABILITY_ATTRIBUTE_MAPPING: '/llm-observability/attribute-mapping',
|
||||
LLM_OBSERVABILITY_BASE: '/llm-observability',
|
||||
LLM_OBSERVABILITY_OVERVIEW: '/llm-observability/overview',
|
||||
LLM_OBSERVABILITY_CONFIGURATION: '/llm-observability/configuration',
|
||||
LLM_OBSERVABILITY_MODEL_PRICING: '/llm-observability/settings/model-pricing',
|
||||
} as const;
|
||||
|
||||
export default ROUTES;
|
||||
|
||||
@@ -43,17 +43,10 @@ type ActionDeps = {
|
||||
aiAssistant?: {
|
||||
open: () => void;
|
||||
};
|
||||
/**
|
||||
* Provided only in development mode. Opens the AuthZ DevTools modal
|
||||
* for testing permission overrides.
|
||||
*/
|
||||
authzDevTools?: {
|
||||
open: () => void;
|
||||
};
|
||||
};
|
||||
|
||||
export function createShortcutActions(deps: ActionDeps): CmdAction[] {
|
||||
const { navigate, handleThemeChange, aiAssistant, authzDevTools } = deps;
|
||||
const { navigate, handleThemeChange, aiAssistant } = deps;
|
||||
|
||||
const actions: CmdAction[] = [
|
||||
{
|
||||
@@ -309,17 +302,5 @@ export function createShortcutActions(deps: ActionDeps): CmdAction[] {
|
||||
});
|
||||
}
|
||||
|
||||
if (authzDevTools) {
|
||||
actions.push({
|
||||
id: 'authz-devtools',
|
||||
name: 'AuthZ DevTools',
|
||||
keywords: 'authz permissions rbac debug devtools override testing',
|
||||
section: 'Dev',
|
||||
icon: <Settings size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
perform: authzDevTools.open,
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
@@ -177,13 +177,7 @@ export default function ConversationView({
|
||||
conversationId={conversationId}
|
||||
messages={messages}
|
||||
isStreaming={isStreamingHere}
|
||||
onSendSuggestedPrompt={(text): void => {
|
||||
handleSend(
|
||||
text,
|
||||
undefined,
|
||||
autoContexts.length > 0 ? autoContexts : undefined,
|
||||
);
|
||||
}}
|
||||
onSendSuggestedPrompt={(text): void => handleSend(text)}
|
||||
/>
|
||||
{showDisclaimer && (
|
||||
<div className={disclaimerClass} role="note" aria-live="polite">
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { MessageContext } from 'api/ai-assistant/chat';
|
||||
import { useAIAssistantStore } from 'container/AIAssistant/store/useAIAssistantStore';
|
||||
import { VariantContext } from 'container/AIAssistant/VariantContext';
|
||||
|
||||
const CHIP_ID = 'recent-errors';
|
||||
const CHIP_TEXT = 'Show me recent errors';
|
||||
|
||||
// Auto-derived page context that a normal (typed) send would attach. The chip
|
||||
// send must forward the exact same array.
|
||||
const mockAutoContexts: MessageContext[] = [
|
||||
{
|
||||
source: 'auto',
|
||||
type: 'dashboard',
|
||||
resourceId: 'dashboard-123',
|
||||
resourceName: 'Checkout dashboard',
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('api/common/logEvent', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('container/AIAssistant/getAutoContexts', () => ({
|
||||
getAutoContexts: jest.fn(() => mockAutoContexts),
|
||||
}));
|
||||
|
||||
jest.mock('container/AIAssistant/hooks/useAIAssistantAnalyticsContext', () => ({
|
||||
normalizePage: (page: string): string => page,
|
||||
useAIAssistantAnalyticsContext: (): unknown => ({ threadId: 'thread-1' }),
|
||||
}));
|
||||
|
||||
// ChatInput is heavy and irrelevant here — the chip path lives entirely in the
|
||||
// empty state. Provide a lightweight stub plus the `autoContextKey` named export
|
||||
// ConversationView imports for its dismissed-context filter.
|
||||
jest.mock('container/AIAssistant/components/ChatInput', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => <div data-testid="chat-input" />,
|
||||
autoContextKey: (): string => '',
|
||||
}));
|
||||
|
||||
jest.mock('container/AIAssistant/components/ConversationSkeleton', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => <div data-testid="skeleton" />,
|
||||
}));
|
||||
|
||||
// VirtualizedMessages renders the real empty-state chips; stub only its
|
||||
// never-rendered-in-empty-state children and the virtual list.
|
||||
jest.mock('components/Noz/Noz', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => <div data-testid="noz" />,
|
||||
}));
|
||||
|
||||
jest.mock('container/AIAssistant/components/MessageBubble', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
jest.mock('container/AIAssistant/components/StreamingMessage', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
jest.mock('react-virtuoso', () => ({
|
||||
__esModule: true,
|
||||
Virtuoso: (): null => null,
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'container/AIAssistant/components/VirtualizedMessages/useEmptyStateChips',
|
||||
() => ({
|
||||
useEmptyStateChips: (): { chips: { id: string; text: string }[] } => ({
|
||||
chips: [{ id: CHIP_ID, text: CHIP_TEXT }],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line import/first
|
||||
import ConversationView from '../ConversationView';
|
||||
|
||||
const CONVERSATION_ID = 'conv-1';
|
||||
|
||||
function renderView(variant: 'panel' | 'page' | 'modal'): {
|
||||
getByTestId: (id: string) => HTMLElement;
|
||||
} {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={['/dashboard/dashboard-123']}>
|
||||
<VariantContext.Provider value={variant}>
|
||||
<ConversationView conversationId={CONVERSATION_ID} />
|
||||
</VariantContext.Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('ConversationView — empty-state chip context', () => {
|
||||
let sendMessage: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
sendMessage = jest.fn();
|
||||
useAIAssistantStore.setState({
|
||||
conversations: {
|
||||
[CONVERSATION_ID]: {
|
||||
id: CONVERSATION_ID,
|
||||
messages: [],
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
},
|
||||
},
|
||||
streams: {},
|
||||
activeConversationId: CONVERSATION_ID,
|
||||
isLoadingThread: false,
|
||||
sendMessage,
|
||||
} as unknown as Partial<ReturnType<typeof useAIAssistantStore.getState>>);
|
||||
});
|
||||
|
||||
it('forwards the page auto-contexts when a chip is clicked (embedded variant)', () => {
|
||||
const { getByTestId } = renderView('panel');
|
||||
|
||||
fireEvent.click(getByTestId(`empty-state-chip-${CHIP_ID}`));
|
||||
|
||||
// The chip send must carry the same auto-contexts a typed message would.
|
||||
expect(sendMessage).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
CHIP_TEXT,
|
||||
undefined,
|
||||
mockAutoContexts,
|
||||
);
|
||||
});
|
||||
|
||||
it('sends undefined contexts on the standalone page (no page context to attach)', () => {
|
||||
const { getByTestId } = renderView('page');
|
||||
|
||||
fireEvent.click(getByTestId(`empty-state-chip-${CHIP_ID}`));
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessage).toHaveBeenCalledWith(CHIP_TEXT, undefined, undefined);
|
||||
});
|
||||
});
|
||||
@@ -163,23 +163,12 @@
|
||||
}
|
||||
|
||||
&.filter-visible {
|
||||
// Width is owned by ResizableBox (inline style); this section is the
|
||||
// ResizableBox root, so it stays position: relative for the drag handle.
|
||||
.api-quick-filter-left-section {
|
||||
.resizable-box__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.quick-filters-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.api-module-right-section {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
width: calc(100% - 260px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,49 +4,21 @@ import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import { ResizableBox } from 'periscope/components/ResizableBox';
|
||||
import usePanelWidth from 'periscope/components/ResizableBox/usePanelWidth';
|
||||
|
||||
import DomainList from './Domains/DomainList';
|
||||
|
||||
import './Explorer.styles.scss';
|
||||
|
||||
const QUICK_FILTERS_DEFAULT_WIDTH = 260;
|
||||
const QUICK_FILTERS_MIN_WIDTH = 240;
|
||||
const QUICK_FILTERS_MAX_WIDTH = 500;
|
||||
|
||||
function Explorer(): JSX.Element {
|
||||
useEffect(() => {
|
||||
logEvent('API Monitoring: Landing page visited', {});
|
||||
}, []);
|
||||
|
||||
const {
|
||||
initialWidth: quickFiltersInitialWidth,
|
||||
persistWidth: persistQuickFiltersWidth,
|
||||
} = usePanelWidth({
|
||||
storageKey: LOCALSTORAGE.QUICK_FILTERS_WIDTH_API_MONITORING,
|
||||
defaultWidth: QUICK_FILTERS_DEFAULT_WIDTH,
|
||||
minWidth: QUICK_FILTERS_MIN_WIDTH,
|
||||
maxWidth: QUICK_FILTERS_MAX_WIDTH,
|
||||
});
|
||||
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<div className={cx('api-monitoring-page', 'filter-visible')}>
|
||||
<ResizableBox
|
||||
handle="right"
|
||||
defaultWidth={QUICK_FILTERS_DEFAULT_WIDTH}
|
||||
initialWidth={quickFiltersInitialWidth}
|
||||
minWidth={QUICK_FILTERS_MIN_WIDTH}
|
||||
maxWidth={QUICK_FILTERS_MAX_WIDTH}
|
||||
onResize={persistQuickFiltersWidth}
|
||||
resetToDefaultOnDoubleClick
|
||||
withHandle
|
||||
className="api-quick-filter-left-section"
|
||||
handleTestId="quick-filters-resize-handle"
|
||||
>
|
||||
<section className="api-quick-filter-left-section">
|
||||
<QuickFilters
|
||||
className="qf-api-monitoring"
|
||||
source={QuickFiltersSource.API_MONITORING}
|
||||
@@ -55,7 +27,7 @@ function Explorer(): JSX.Element {
|
||||
showQueryName={false}
|
||||
handleFilterVisibilityChange={(): void => {}}
|
||||
/>
|
||||
</ResizableBox>
|
||||
</section>
|
||||
<DomainList />
|
||||
</div>
|
||||
</Sentry.ErrorBoundary>
|
||||
|
||||
@@ -149,18 +149,6 @@
|
||||
line-height: 22px; /* 157.143% */
|
||||
letter-spacing: -0.07px;
|
||||
padding: 20px 16px 0px 16px;
|
||||
/* Preserve author-entered line breaks in the description. */
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
a {
|
||||
color: var(--accent-primary);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-variables {
|
||||
|
||||
@@ -43,7 +43,6 @@ import { sortLayout } from 'providers/Dashboard/util';
|
||||
import { DashboardData } from 'types/api/dashboard/getAll';
|
||||
import { Props } from 'types/api/dashboard/update';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import { linkifyText } from 'utils/linkifyText';
|
||||
import { ComponentTypes } from 'utils/permission';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
@@ -516,9 +515,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
</div>
|
||||
)}
|
||||
{!isEmpty(description) && (
|
||||
<section className="dashboard-description-section">
|
||||
{linkifyText(description ?? '')}
|
||||
</section>
|
||||
<section className="dashboard-description-section">{description}</section>
|
||||
)}
|
||||
|
||||
{!isEmpty(dashboardVariables) && (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||
import type { PrecisionOption } from 'components/Graph/types';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
|
||||
@@ -28,7 +27,7 @@ interface PieArcProps {
|
||||
fill: string;
|
||||
onEnter: (slice: PieSlice, centroidX: number, centroidY: number) => void;
|
||||
onLeave: () => void;
|
||||
onClick?: (slice: PieSlice, event: ReactMouseEvent) => void;
|
||||
onClick?: (slice: PieSlice) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,7 +72,7 @@ export default function PieArc({
|
||||
<g
|
||||
onMouseEnter={(): void => onEnter(slice, centroidX, centroidY)}
|
||||
onMouseLeave={onLeave}
|
||||
onClick={(event): void => onClick?.(slice, event)}
|
||||
onClick={(): void => onClick?.(slice)}
|
||||
>
|
||||
<path d={arcPath} fill={fill} />
|
||||
{shouldShowLabel && (
|
||||
|
||||
@@ -80,7 +80,6 @@ describe('PieArc', () => {
|
||||
expect(onLeave).toHaveBeenCalledTimes(1);
|
||||
|
||||
fireEvent.click(g);
|
||||
// onClick now also receives the DOM event (for drill-down popover positioning).
|
||||
expect(onClick).toHaveBeenCalledWith(SLICE, expect.anything());
|
||||
expect(onClick).toHaveBeenCalledWith(SLICE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PrecisionOption } from 'components/Graph/types';
|
||||
import {
|
||||
@@ -80,10 +79,6 @@ export interface PieSlice {
|
||||
label: string;
|
||||
value: number;
|
||||
color: string;
|
||||
/** Source query of the slice's value column — the drill-down target (present for V2 panels). */
|
||||
queryName?: string;
|
||||
/** Group-by key→value of the slice's source row, used to build drill-down filters. */
|
||||
labels?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +99,7 @@ export interface PieChartProps {
|
||||
* (shared GRAPH_VISIBILITY_STATES, keyed by label). Omit to disable persistence.
|
||||
*/
|
||||
id?: string;
|
||||
/** Fired when a slice's arc is clicked; carries the DOM event for popover positioning. */
|
||||
onSliceClick?: (slice: PieSlice, event: ReactMouseEvent) => void;
|
||||
/** Fired when a slice (or its legend entry) is clicked. */
|
||||
onSliceClick?: (slice: PieSlice) => void;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
// Stacked children (the FullView / standalone graph-manager) sit below the chart
|
||||
|
||||
@@ -41,28 +41,6 @@
|
||||
|
||||
.steps-container {
|
||||
width: 80%;
|
||||
|
||||
.alert-query-section-container {
|
||||
--query-builder-v2-color: var(--l2-foreground);
|
||||
--query-builder-v2-background-color: var(--l3-background);
|
||||
--query-builder-v2-border-color: var(--l3-border);
|
||||
--query-builder-v2-selection-background-color: var(--l2-background);
|
||||
--query-builder-v2-chip-decorator-background-color: var(--l2-background);
|
||||
--query-builder-v2-placeholder-color: var(--l3-foreground);
|
||||
--query-builder-v2-selected-background-color: var(--l3-foreground);
|
||||
--query-search-background-color: var(--l3-background);
|
||||
--query-search-background-color-selection: var(--l3-background);
|
||||
--input-with-label-border-color: var(--l3-border);
|
||||
--input-with-label-background-color: var(--l3-background);
|
||||
--input-with-label-color: var(--l2-foreground);
|
||||
--query-builder-v2-toggle-group-background-color: var(--l3-background);
|
||||
--query-builder-v2-toggle-group-border-color: var(--l3-border);
|
||||
--query-builder-v2-toggle-group-active-background-color: var(--l2-background);
|
||||
--query-builder-v2-toggle-group-background-color-hover: var(--l3-background);
|
||||
--input-height: 36px;
|
||||
--input-hover-background: var(--l3-background);
|
||||
--input-hover-border-color: var(--internal-ant-border-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.qb-chart-preview-container {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
.wrapper :global(button[data-color]) {
|
||||
--checkbox-checked-background: var(--series-color);
|
||||
--checkbox-border-color: var(--series-color);
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import { Checkbox } from '@signozhq/ui/checkbox';
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
import { CheckBoxProps } from '../types';
|
||||
import styles from './CustomCheckBox.module.scss';
|
||||
|
||||
function CustomCheckBox({
|
||||
data,
|
||||
@@ -16,11 +15,12 @@ function CustomCheckBox({
|
||||
const isChecked = graphVisibilityState[index] || false;
|
||||
|
||||
const colorStyle = {
|
||||
'--series-color': color,
|
||||
'--checkbox-checked-background': color,
|
||||
'--checkbox-border-color': color,
|
||||
} as CSSProperties;
|
||||
|
||||
return (
|
||||
<span className={styles.wrapper} style={colorStyle}>
|
||||
<span style={colorStyle}>
|
||||
<Checkbox
|
||||
onChange={(checked): void => checkBoxOnChangeHandler(checked, index)}
|
||||
value={isChecked}
|
||||
|
||||
@@ -3,30 +3,6 @@
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
--query-builder-v2-color: var(--l2-foreground);
|
||||
--query-builder-v2-background-color: var(--l3-background);
|
||||
--query-builder-v2-border-color: var(--l3-border);
|
||||
--query-builder-v2-selection-background-color: var(--l2-background);
|
||||
--query-builder-v2-chip-decorator-background-color: var(--l2-background);
|
||||
--query-builder-v2-placeholder-color: var(--l3-foreground);
|
||||
--query-builder-v2-selected-background-color: var(--l3-foreground);
|
||||
--query-search-background-color: var(--l3-background);
|
||||
--query-search-background-color-selection: var(--l3-background);
|
||||
--input-with-label-border-color: var(--l3-border);
|
||||
--input-with-label-background-color: var(--l3-background);
|
||||
--input-with-label-color: var(--l2-foreground);
|
||||
--query-builder-v2-toggle-group-background-color: var(--l3-background);
|
||||
--query-builder-v2-toggle-group-border-color: var(--l3-border);
|
||||
--query-builder-v2-toggle-group-active-background-color: var(--l2-background);
|
||||
--query-builder-v2-toggle-group-background-color-hover: var(--l3-background);
|
||||
--input-height: 36px;
|
||||
--input-hover-background: var(--l3-background);
|
||||
--input-hover-border-color: var(--internal-ant-border-color-hover);
|
||||
--input-focus-border-color: var(--internal-ant-border-color-hover);
|
||||
--input-background: var(--l3-background);
|
||||
--input-focus-background: var(--l3-background);
|
||||
--input-border-color: var(--l3-border);
|
||||
|
||||
.full-view-header-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -66,7 +66,6 @@ function WidgetGraphComponent({
|
||||
customOnRowClick,
|
||||
customTimeRangeWindowForCoRelation,
|
||||
enableDrillDown,
|
||||
hidePagination,
|
||||
}: WidgetGraphComponentProps): JSX.Element {
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
@@ -431,7 +430,6 @@ function WidgetGraphComponent({
|
||||
customSeries={customSeries}
|
||||
customOnRowClick={customOnRowClick}
|
||||
enableDrillDown={enableDrillDown}
|
||||
hidePagination={hidePagination}
|
||||
onColumnWidthsChange={onColumnWidthsChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -42,8 +42,6 @@ export interface WidgetGraphComponentProps {
|
||||
customOnRowClick?: (record: RowData) => void;
|
||||
customTimeRangeWindowForCoRelation?: string | undefined;
|
||||
enableDrillDown?: boolean;
|
||||
/** Hide list-panel pagination controls (e.g. public dashboards, where paging isn't supported). */
|
||||
hidePagination?: boolean;
|
||||
}
|
||||
|
||||
export interface GridCardGraphProps {
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button, Skeleton } from 'antd';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useListDashboardsForUserV2 } from 'api/generated/services/dashboard';
|
||||
import {
|
||||
DashboardtypesListOrderDTO,
|
||||
DashboardtypesListSortDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { ArrowRight, ArrowUpRight, Plus } from '@signozhq/icons';
|
||||
import Card from 'periscope/components/Card/Card';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
@@ -22,13 +17,6 @@ import dialsUrl from '@/assets/Icons/dials.svg';
|
||||
|
||||
import { getItemIcon } from '../constants';
|
||||
|
||||
// The five most-recent dashboards, normalised across the v1 and v2 list APIs.
|
||||
interface RecentDashboard {
|
||||
id: string;
|
||||
title: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export default function Dashboards({
|
||||
onUpdateChecklistDoneItem,
|
||||
loadingUserPreferences,
|
||||
@@ -38,58 +26,33 @@ export default function Dashboards({
|
||||
}): JSX.Element {
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const { user } = useAppContext();
|
||||
const isDashboardV2 = useIsDashboardV2();
|
||||
|
||||
// Fetch the recent dashboards from whichever API the `use_dashboard_v2` flag
|
||||
// selects; the inactive one stays disabled so it never fires.
|
||||
const [sortedDashboards, setSortedDashboards] = useState<Dashboard[]>([]);
|
||||
|
||||
// Fetch Dashboards
|
||||
const {
|
||||
data: v1List,
|
||||
isLoading: v1Loading,
|
||||
isError: v1Error,
|
||||
} = useGetAllDashboard({ enabled: !isDashboardV2 });
|
||||
|
||||
const {
|
||||
data: v2List,
|
||||
isLoading: v2Loading,
|
||||
isError: v2Error,
|
||||
} = useListDashboardsForUserV2(
|
||||
{
|
||||
sort: DashboardtypesListSortDTO.updated_at,
|
||||
order: DashboardtypesListOrderDTO.desc,
|
||||
limit: 5,
|
||||
offset: 0,
|
||||
},
|
||||
{ query: { enabled: isDashboardV2 } },
|
||||
);
|
||||
|
||||
const isDashboardListLoading = isDashboardV2 ? v2Loading : v1Loading;
|
||||
const isDashboardListError = isDashboardV2 ? v2Error : v1Error;
|
||||
|
||||
const sortedDashboards = useMemo<RecentDashboard[]>(() => {
|
||||
if (isDashboardV2) {
|
||||
return (v2List?.data?.dashboards ?? []).map((d) => ({
|
||||
id: d.id,
|
||||
title: d.spec?.display?.name ?? d.name,
|
||||
tags: (d.tags ?? []).map((t) => (t.value ? `${t.key}:${t.value}` : t.key)),
|
||||
}));
|
||||
}
|
||||
return [...(v1List?.data ?? [])]
|
||||
.sort(
|
||||
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
|
||||
)
|
||||
.slice(0, 5)
|
||||
.map((d) => ({
|
||||
id: d.id,
|
||||
title: d.data.title,
|
||||
tags: d.data.tags ?? [],
|
||||
}));
|
||||
}, [isDashboardV2, v1List, v2List]);
|
||||
data: dashboardsList,
|
||||
isLoading: isDashboardListLoading,
|
||||
isError: isDashboardListError,
|
||||
} = useGetAllDashboard();
|
||||
|
||||
useEffect(() => {
|
||||
if (!dashboardsList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sortedDashboards = dashboardsList.data.sort((a, b) => {
|
||||
const aUpdateAt = new Date(a.updatedAt).getTime();
|
||||
const bUpdateAt = new Date(b.updatedAt).getTime();
|
||||
return bUpdateAt - aUpdateAt;
|
||||
});
|
||||
|
||||
if (sortedDashboards.length > 0 && !loadingUserPreferences) {
|
||||
onUpdateChecklistDoneItem('SETUP_DASHBOARDS');
|
||||
}
|
||||
}, [sortedDashboards, onUpdateChecklistDoneItem, loadingUserPreferences]);
|
||||
|
||||
setSortedDashboards(sortedDashboards.slice(0, 5));
|
||||
}, [dashboardsList, onUpdateChecklistDoneItem, loadingUserPreferences]);
|
||||
|
||||
const emptyStateCard = (): JSX.Element => (
|
||||
<div className="empty-state-container">
|
||||
@@ -150,7 +113,7 @@ export default function Dashboards({
|
||||
event.stopPropagation();
|
||||
logEvent('Homepage: Dashboard clicked', {
|
||||
dashboardId: dashboard.id,
|
||||
dashboardName: dashboard.title,
|
||||
dashboardName: dashboard.data.title,
|
||||
});
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
openInNewTab(getLink());
|
||||
@@ -180,12 +143,12 @@ export default function Dashboards({
|
||||
/>
|
||||
|
||||
<div className="alert-rule-item-name home-data-item-name">
|
||||
{dashboard.title}
|
||||
{dashboard.data.title}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="alert-rule-item-description home-data-item-tag">
|
||||
{dashboard.tags.map((tag) => (
|
||||
{dashboard.data.tags?.map((tag) => (
|
||||
<Badge color="sienna" variant="outline" key={tag}>
|
||||
{tag}
|
||||
</Badge>
|
||||
|
||||
@@ -6,7 +6,6 @@ import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import K8sBaseDetails from 'container/InfraMonitoringK8s/Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from 'container/InfraMonitoringK8s/Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from 'container/InfraMonitoringK8s/Base/types';
|
||||
@@ -17,8 +16,6 @@ import {
|
||||
} from 'container/InfraMonitoringK8s/hooks';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import { ResizableBox } from 'periscope/components/ResizableBox';
|
||||
import usePanelWidth from 'periscope/components/ResizableBox/usePanelWidth';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
@@ -43,21 +40,8 @@ import { getHostsQuickFiltersConfig } from './utils';
|
||||
import styles from './InfraMonitoringHosts.module.scss';
|
||||
import { ArrowUpToLine, Filter } from '@signozhq/icons';
|
||||
|
||||
const QUICK_FILTERS_DEFAULT_WIDTH = 280;
|
||||
const QUICK_FILTERS_MIN_WIDTH = 240;
|
||||
const QUICK_FILTERS_MAX_WIDTH = 500;
|
||||
|
||||
function Hosts(): JSX.Element {
|
||||
const [showFilters, setShowFilters] = useState(true);
|
||||
const {
|
||||
initialWidth: quickFiltersInitialWidth,
|
||||
persistWidth: persistQuickFiltersWidth,
|
||||
} = usePanelWidth({
|
||||
storageKey: LOCALSTORAGE.QUICK_FILTERS_WIDTH_INFRA,
|
||||
defaultWidth: QUICK_FILTERS_DEFAULT_WIDTH,
|
||||
minWidth: QUICK_FILTERS_MIN_WIDTH,
|
||||
maxWidth: QUICK_FILTERS_MAX_WIDTH,
|
||||
});
|
||||
const [, setCurrentPage] = useInfraMonitoringPageListing();
|
||||
const [urlFilters, setUrlFilters] = useInfraMonitoringFiltersK8s();
|
||||
|
||||
@@ -155,18 +139,7 @@ function Hosts(): JSX.Element {
|
||||
<div className={styles.infraMonitoringContainer}>
|
||||
<div className={styles.infraContentRow}>
|
||||
{showFilters && (
|
||||
<ResizableBox
|
||||
handle="right"
|
||||
defaultWidth={QUICK_FILTERS_DEFAULT_WIDTH}
|
||||
initialWidth={quickFiltersInitialWidth}
|
||||
minWidth={QUICK_FILTERS_MIN_WIDTH}
|
||||
maxWidth={QUICK_FILTERS_MAX_WIDTH}
|
||||
onResize={persistQuickFiltersWidth}
|
||||
resetToDefaultOnDoubleClick
|
||||
withHandle
|
||||
className={styles.quickFiltersContainer}
|
||||
handleTestId="quick-filters-resize-handle"
|
||||
>
|
||||
<div className={styles.quickFiltersContainer}>
|
||||
<div className={styles.quickFiltersContainerHeader}>
|
||||
<Typography.Text>Filters</Typography.Text>
|
||||
<Tooltip title="Collapse Filters">
|
||||
@@ -183,7 +156,7 @@ function Hosts(): JSX.Element {
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleQuickFiltersChange}
|
||||
/>
|
||||
</ResizableBox>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`${styles.listContainer}${
|
||||
@@ -216,6 +189,8 @@ function Hosts(): JSX.Element {
|
||||
queryKeyPrefix="hosts"
|
||||
tabsConfig={{
|
||||
showEvents: false,
|
||||
showContainers: true,
|
||||
showProcesses: true,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -44,20 +44,26 @@
|
||||
}
|
||||
|
||||
.quickFiltersContainer {
|
||||
// Width is owned by ResizableBox (inline style). No overflow here: the panel
|
||||
// must not scroll (QuickFilters scrolls its own list internally) — an
|
||||
// overflow on the root would also clip the edge-mounted resize grip.
|
||||
flex-shrink: 0;
|
||||
width: 280px;
|
||||
min-width: 280px;
|
||||
border-right: 1px solid var(--l1-border);
|
||||
overflow-y: auto;
|
||||
|
||||
:global(.resizable-box__content) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.1rem;
|
||||
height: 0.1rem;
|
||||
}
|
||||
|
||||
:global(.quick-filters-container) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: colox-mix(var(--accent-primary), var(--bg-vanilla-100), 20%);
|
||||
}
|
||||
|
||||
:global(.ant-collapse-header) {
|
||||
@@ -136,9 +142,7 @@
|
||||
}
|
||||
|
||||
.listContainerFiltersVisible {
|
||||
// Width is driven by flex now that the filters panel is resizable; the list
|
||||
// fills whatever space the (variable-width) panel leaves.
|
||||
max-width: 100%;
|
||||
max-width: calc(100% - 280px);
|
||||
}
|
||||
|
||||
.quickFiltersToggleContainer {
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import K8sBaseDetails from 'container/InfraMonitoringK8sV2/Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from 'container/InfraMonitoringK8sV2/Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from 'container/InfraMonitoringK8sV2/Base/types';
|
||||
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants';
|
||||
import {
|
||||
useInfraMonitoringFiltersK8s,
|
||||
useInfraMonitoringPageListing,
|
||||
} from 'container/InfraMonitoringK8sV2/hooks';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import { ResizableBox } from 'periscope/components/ResizableBox';
|
||||
import usePanelWidth from 'periscope/components/ResizableBox/usePanelWidth';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import {
|
||||
fetchHostEntityData,
|
||||
fetchHostListData,
|
||||
getHostMetricsQueryPayload,
|
||||
hostDetailsMetadataConfig,
|
||||
hostGetEntityName,
|
||||
hostGetSelectedItemFilters,
|
||||
hostInitialEventsFilter,
|
||||
hostInitialLogTracesFilter,
|
||||
hostWidgetInfo,
|
||||
} from './constants';
|
||||
import {
|
||||
getHostItemKey,
|
||||
getHostRowKey,
|
||||
hostColumnsConfig,
|
||||
} from './table.config';
|
||||
import { getHostsQuickFiltersConfig } from './utils';
|
||||
|
||||
import styles from './InfraMonitoringHosts.module.scss';
|
||||
import { ArrowUpToLine, Filter } from '@signozhq/icons';
|
||||
|
||||
const QUICK_FILTERS_DEFAULT_WIDTH = 280;
|
||||
const QUICK_FILTERS_MIN_WIDTH = 240;
|
||||
const QUICK_FILTERS_MAX_WIDTH = 500;
|
||||
|
||||
function Hosts(): JSX.Element {
|
||||
const [showFilters, setShowFilters] = useState(true);
|
||||
const {
|
||||
initialWidth: quickFiltersInitialWidth,
|
||||
persistWidth: persistQuickFiltersWidth,
|
||||
} = usePanelWidth({
|
||||
storageKey: LOCALSTORAGE.QUICK_FILTERS_WIDTH_INFRA,
|
||||
defaultWidth: QUICK_FILTERS_DEFAULT_WIDTH,
|
||||
minWidth: QUICK_FILTERS_MIN_WIDTH,
|
||||
maxWidth: QUICK_FILTERS_MAX_WIDTH,
|
||||
});
|
||||
const [, setCurrentPage] = useInfraMonitoringPageListing();
|
||||
const [urlFilters, setUrlFilters] = useInfraMonitoringFiltersK8s();
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
index: 0,
|
||||
query: currentQuery.builder.queryData[0],
|
||||
entityVersion: '',
|
||||
});
|
||||
|
||||
// Track previous urlFilters to only sync when the value actually changes
|
||||
// (not when handleChangeQueryData changes due to query updates)
|
||||
const prevUrlFiltersRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const currentFiltersJson = urlFilters ? JSON.stringify(urlFilters) : null;
|
||||
|
||||
// Only sync if urlFilters value has actually changed
|
||||
if (prevUrlFiltersRef.current !== currentFiltersJson) {
|
||||
prevUrlFiltersRef.current = currentFiltersJson;
|
||||
// Sync filters to query builder, using empty filter when urlFilters is null
|
||||
handleChangeQueryData('filters', urlFilters || { items: [], op: 'and' });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [urlFilters]); // handleChangeQueryData intentionally omitted - we call the current version but don't re-run when it changes
|
||||
|
||||
const handleFilterVisibilityChange = (): void => {
|
||||
setShowFilters(!showFilters);
|
||||
};
|
||||
|
||||
const handleQuickFiltersChange = (query: Query): void => {
|
||||
const filters = query.builder.queryData[0].filters;
|
||||
// Nuqs batches these calls into a single URL update
|
||||
// The useEffect will sync filters to query builder
|
||||
setUrlFilters(filters || null);
|
||||
setCurrentPage(1);
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
entity: InfraMonitoringEvents.HostEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
});
|
||||
};
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
filters.orderBy ||= {
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
return fetchHostListData(filters, signal);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: Parameters<typeof fetchHostEntityData>[0],
|
||||
signal?: AbortSignal,
|
||||
) => fetchHostEntityData(filters, signal),
|
||||
[],
|
||||
);
|
||||
|
||||
const getSelectedItemFilters = useCallback(
|
||||
(selectedItem: string) =>
|
||||
hostGetSelectedItemFilters(selectedItem, dotMetricsEnabled),
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const getInitialLogTracesFilters = useCallback(
|
||||
(host: import('api/infraMonitoring/getHostLists').HostData) =>
|
||||
hostInitialLogTracesFilter(host, dotMetricsEnabled),
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const controlListPrefix = !showFilters ? (
|
||||
<div className={styles.quickFiltersToggleContainer}>
|
||||
<Button
|
||||
className="periscope-btn ghost"
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={handleFilterVisibilityChange}
|
||||
>
|
||||
<Filter size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
) : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.infraMonitoringContainer}>
|
||||
<div className={styles.infraContentRow}>
|
||||
{showFilters && (
|
||||
<ResizableBox
|
||||
handle="right"
|
||||
defaultWidth={QUICK_FILTERS_DEFAULT_WIDTH}
|
||||
initialWidth={quickFiltersInitialWidth}
|
||||
minWidth={QUICK_FILTERS_MIN_WIDTH}
|
||||
maxWidth={QUICK_FILTERS_MAX_WIDTH}
|
||||
onResize={persistQuickFiltersWidth}
|
||||
resetToDefaultOnDoubleClick
|
||||
withHandle
|
||||
className={styles.quickFiltersContainer}
|
||||
handleTestId="quick-filters-resize-handle"
|
||||
>
|
||||
<div className={styles.quickFiltersContainerHeader}>
|
||||
<Typography.Text>Filters</Typography.Text>
|
||||
<Tooltip title="Collapse Filters">
|
||||
<ArrowUpToLine
|
||||
style={{ rotate: '270deg', cursor: 'pointer' }}
|
||||
onClick={handleFilterVisibilityChange}
|
||||
size="md"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={getHostsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleQuickFiltersChange}
|
||||
/>
|
||||
</ResizableBox>
|
||||
)}
|
||||
<div
|
||||
className={`${styles.listContainer}${
|
||||
showFilters ? ` ${styles.listContainerFiltersVisible}` : ''
|
||||
}`}
|
||||
>
|
||||
<K8sBaseList
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={InfraMonitoringEntity.HOSTS}
|
||||
tableColumns={hostColumnsConfig}
|
||||
fetchListData={fetchListData}
|
||||
getRowKey={getHostRowKey}
|
||||
getItemKey={getHostItemKey}
|
||||
eventCategory={InfraMonitoringEvents.HostEntity}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<K8sBaseDetails
|
||||
category={InfraMonitoringEntity.HOSTS}
|
||||
eventCategory={InfraMonitoringEvents.HostEntity}
|
||||
getSelectedItemFilters={getSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={hostGetEntityName}
|
||||
getInitialLogTracesFilters={getInitialLogTracesFilters}
|
||||
getInitialEventsFilters={hostInitialEventsFilter}
|
||||
metadataConfig={hostDetailsMetadataConfig}
|
||||
entityWidgetInfo={hostWidgetInfo}
|
||||
getEntityQueryPayload={getHostMetricsQueryPayload}
|
||||
queryKeyPrefix="hosts"
|
||||
tabsConfig={{
|
||||
showEvents: false,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Hosts;
|
||||
@@ -1,171 +0,0 @@
|
||||
.infraMonitoringContainer {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.infraContentRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: calc(100vh - 45px);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
|
||||
:global(.periscope-btn) {
|
||||
&.ghost:not(:disabled) {
|
||||
border: none;
|
||||
background: transparent;
|
||||
|
||||
&:hover {
|
||||
background: transparent;
|
||||
color: var(--bg-robin-500) !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.1rem;
|
||||
height: 0.1rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: colox-mix(var(--accent-primary), var(--bg-vanilla-100), 20%);
|
||||
}
|
||||
}
|
||||
|
||||
.quickFiltersContainer {
|
||||
// Width is owned by ResizableBox (inline style). No overflow here: the panel
|
||||
// must not scroll (QuickFilters scrolls its own list internally) — an
|
||||
// overflow on the root would also clip the edge-mounted resize grip.
|
||||
flex-shrink: 0;
|
||||
border-right: 1px solid var(--l1-border);
|
||||
|
||||
:global(.resizable-box__content) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:global(.quick-filters-container) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
:global(.ant-collapse-header) {
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:global(.ant-collapse-content-box) {
|
||||
padding: 0 !important;
|
||||
padding-block: 0 !important;
|
||||
|
||||
:global(.quick-filters .checkbox-filter) {
|
||||
padding-left: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.quick-filters) {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.1rem;
|
||||
height: 0.1rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: colox-mix(var(--accent-primary), var(--bg-vanilla-100), 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quickFiltersContainerHeader {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.listContainer {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
> * {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
> :global(.ant-table-wrapper) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
:global(.ant-spin-container) {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:global(.ant-table),
|
||||
:global(.ant-table-container) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.listContainerFiltersVisible {
|
||||
// Width is driven by flex now that the filters panel is resizable; the list
|
||||
// fills whatever space the (variable-width) panel leaves.
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.quickFiltersToggleContainer {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.infraMonitoringTags {
|
||||
width: fit-content;
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.44px;
|
||||
text-transform: uppercase;
|
||||
border-radius: 50px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.tagsActive {
|
||||
color: var(--bg-forest-500, #25e192);
|
||||
border: 1px solid rgba(37, 225, 146, 0.2);
|
||||
background: rgba(37, 225, 146, 0.1);
|
||||
}
|
||||
|
||||
.tagsInactive {
|
||||
color: var(--bg-slate-50, #62687c);
|
||||
border: 1px solid rgba(98, 104, 124, 0.2);
|
||||
background: rgba(98, 104, 124, 0.1);
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import {
|
||||
getHostLists,
|
||||
HostData,
|
||||
HostListPayload,
|
||||
} from 'api/infraMonitoring/getHostLists';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsFilters,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from 'container/InfraMonitoringK8sV2/Base/K8sBaseDetails';
|
||||
import { K8sBaseFilters } from 'container/InfraMonitoringK8sV2/Base/types';
|
||||
import {
|
||||
getHostQueryPayload,
|
||||
hostWidgetInfo,
|
||||
} from 'container/LogDetailedView/InfraMetrics/constants';
|
||||
import {
|
||||
TagFilter,
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { getHostListsQuery } from './utils';
|
||||
|
||||
import infraHostsStyles from './InfraMonitoringHosts.module.scss';
|
||||
|
||||
export function getProgressColor(percent: number): string {
|
||||
if (percent >= 90) {
|
||||
return Color.BG_SAKURA_500;
|
||||
}
|
||||
if (percent >= 60) {
|
||||
return Color.BG_AMBER_500;
|
||||
}
|
||||
return Color.BG_FOREST_500;
|
||||
}
|
||||
|
||||
export function getMemoryProgressColor(percent: number): string {
|
||||
if (percent >= 90) {
|
||||
return Color.BG_CHERRY_500;
|
||||
}
|
||||
if (percent >= 60) {
|
||||
return Color.BG_AMBER_500;
|
||||
}
|
||||
return Color.BG_FOREST_500;
|
||||
}
|
||||
|
||||
export const hostDetailsMetadataConfig: K8sDetailsMetadataConfig<HostData>[] = [
|
||||
{
|
||||
label: 'STATUS',
|
||||
getValue: (h): string => (h.active ? 'ACTIVE' : 'INACTIVE'),
|
||||
render: (value, h): React.ReactNode => (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`${infraHostsStyles.infraMonitoringTags} ${
|
||||
h.active ? infraHostsStyles.tagsActive : infraHostsStyles.tagsInactive
|
||||
}`}
|
||||
>
|
||||
{value}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'OPERATING SYSTEM',
|
||||
getValue: (h): string => h.os || '-',
|
||||
render: (value): React.ReactNode =>
|
||||
value !== '-' ? (
|
||||
<Badge variant="outline" className={infraHostsStyles.infraMonitoringTags}>
|
||||
{value}
|
||||
</Badge>
|
||||
) : (
|
||||
<Typography.Text>-</Typography.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'CPU USAGE',
|
||||
getValue: (h): number => h.cpu * 100,
|
||||
render: (value): React.ReactNode => (
|
||||
<Progress
|
||||
percent={Number(Number(value).toFixed(1))}
|
||||
strokeColor={getProgressColor(Number(value))}
|
||||
showInfo
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'MEMORY USAGE',
|
||||
getValue: (h): number => h.memory * 100,
|
||||
render: (value): React.ReactNode => (
|
||||
<Progress
|
||||
percent={Number(Number(value).toFixed(1))}
|
||||
strokeColor={getMemoryProgressColor(Number(value))}
|
||||
showInfo
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export function getHostMetricsQueryPayload(
|
||||
host: HostData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): ReturnType<typeof getHostQueryPayload> {
|
||||
return getHostQueryPayload(host.hostName, start, end, dotMetricsEnabled);
|
||||
}
|
||||
|
||||
export { hostWidgetInfo };
|
||||
|
||||
export function hostGetSelectedItemFilters(
|
||||
selectedItem: string,
|
||||
dotMetricsEnabled: boolean,
|
||||
): TagFilter {
|
||||
const hostKey = dotMetricsEnabled ? 'host.name' : 'host_name';
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [createFilterItem(hostKey, selectedItem)],
|
||||
};
|
||||
}
|
||||
|
||||
export function hostInitialLogTracesFilter(
|
||||
host: HostData,
|
||||
dotMetricsEnabled: boolean,
|
||||
): TagFilterItem[] {
|
||||
const hostKey = dotMetricsEnabled ? 'host.name' : 'host_name';
|
||||
return [createFilterItem(hostKey, host.hostName || '')];
|
||||
}
|
||||
|
||||
export function hostInitialEventsFilter(_host: HostData): TagFilterItem[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
export const hostGetEntityName = (host: HostData): string => host.hostName;
|
||||
|
||||
export async function fetchHostListData(
|
||||
filters: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: HostData[];
|
||||
total: number;
|
||||
error?: string | null;
|
||||
rawData?: unknown;
|
||||
}> {
|
||||
const baseQuery = getHostListsQuery();
|
||||
const payload: HostListPayload = {
|
||||
...baseQuery,
|
||||
limit: filters.limit,
|
||||
offset: filters.offset,
|
||||
filters: filters.filters ?? { items: [], op: 'and' },
|
||||
orderBy: filters.orderBy,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
groupBy: filters.groupBy ?? [],
|
||||
};
|
||||
|
||||
const response = await getHostLists(payload, signal);
|
||||
|
||||
return {
|
||||
data: response.payload?.data?.records || [],
|
||||
total: response.payload?.data?.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchHostEntityData(
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{ data: HostData | null; error?: string | null }> {
|
||||
const response = await getHostLists(
|
||||
{
|
||||
...getHostListsQuery(),
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
groupBy: [],
|
||||
},
|
||||
signal,
|
||||
);
|
||||
|
||||
const records = response.payload?.data?.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import Hosts from './Hosts';
|
||||
|
||||
function InfraMonitoringHosts(): JSX.Element {
|
||||
const {
|
||||
updateAllQueriesOperators,
|
||||
handleSetConfig,
|
||||
setSupersetQuery,
|
||||
setLastUsedQuery,
|
||||
currentQuery,
|
||||
resetQuery,
|
||||
} = useQueryBuilder();
|
||||
|
||||
useEffect(() => {
|
||||
const newQuery = updateAllQueriesOperators(
|
||||
initialQueriesMap.metrics,
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
DataSource.METRICS,
|
||||
);
|
||||
|
||||
setSupersetQuery(newQuery);
|
||||
setLastUsedQuery(0);
|
||||
handleSetConfig(PANEL_TYPES.TIME_SERIES, DataSource.METRICS);
|
||||
|
||||
return (): void => {
|
||||
setLastUsedQuery(0);
|
||||
};
|
||||
}, [
|
||||
updateAllQueriesOperators,
|
||||
setSupersetQuery,
|
||||
setLastUsedQuery,
|
||||
handleSetConfig,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const updatedCurrentQuery = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: [
|
||||
{
|
||||
...currentQuery.builder.queryData[0],
|
||||
filters: {
|
||||
items: [],
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
resetQuery(updatedCurrentQuery);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<Hosts />
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export default InfraMonitoringHosts;
|
||||
@@ -1,201 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Tooltip } from 'antd';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { HostData } from 'api/infraMonitoring/getHostLists';
|
||||
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
|
||||
import { getGroupByEl } from 'container/InfraMonitoringK8sV2/Base/utils';
|
||||
import {
|
||||
EntityProgressBar,
|
||||
ExpandButtonWrapper,
|
||||
ValidateColumnValueWrapper,
|
||||
} from 'container/InfraMonitoringK8sV2/components';
|
||||
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants';
|
||||
import { useInfraMonitoringGroupBy } from 'container/InfraMonitoringK8sV2/hooks';
|
||||
import EntityGroupHeader from 'container/InfraMonitoringK8sV2/Base/EntityGroupHeader';
|
||||
|
||||
import { HostnameCell } from './utils';
|
||||
|
||||
import styles from './table.module.scss';
|
||||
import { Container, Info } from '@signozhq/icons';
|
||||
|
||||
function hostRowSource(host: HostData): { meta: Record<string, string> } {
|
||||
return {
|
||||
meta: {
|
||||
...(host.meta ?? {}),
|
||||
host_name: host.hostName ?? '',
|
||||
'host.name': host.hostName ?? '',
|
||||
os_type: host.os ?? '',
|
||||
'os.type': host.os ?? '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getHostRowKey(host: HostData): string {
|
||||
return host.hostName || 'unknown';
|
||||
}
|
||||
|
||||
export function getHostItemKey(host: HostData): string {
|
||||
return host.hostName ?? '';
|
||||
}
|
||||
|
||||
function HostGroupCell({ row }: { row: HostData }): JSX.Element {
|
||||
const [groupBy] = useInfraMonitoringGroupBy();
|
||||
const synthetic = hostRowSource(row);
|
||||
return getGroupByEl(synthetic, groupBy) as JSX.Element;
|
||||
}
|
||||
|
||||
export const hostColumnsConfig: TableColumnDef<HostData>[] = [
|
||||
{
|
||||
id: 'hostGroup',
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="HOST GROUP" />,
|
||||
accessorFn: (row): string => row.hostName ?? '',
|
||||
width: { min: 300 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
pin: 'left',
|
||||
visibilityBehavior: 'hidden-on-collapse',
|
||||
cell: ({ row, isExpanded, toggleExpanded }): React.ReactNode => (
|
||||
<ExpandButtonWrapper isExpanded={isExpanded} toggleExpanded={toggleExpanded}>
|
||||
<HostGroupCell row={row} />
|
||||
</ExpandButtonWrapper>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'hostName',
|
||||
header: (): React.ReactNode => (
|
||||
<EntityGroupHeader title="Hostname" icon={<Container size={14} />} />
|
||||
),
|
||||
accessorFn: (row): string => row.hostName ?? '',
|
||||
width: { min: 290 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
pin: 'left',
|
||||
visibilityBehavior: 'hidden-on-expand',
|
||||
cell: ({ value }): React.ReactNode => (
|
||||
<HostnameCell hostName={value as string} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'active',
|
||||
header: (): React.ReactNode => (
|
||||
<div className={styles.statusHeader}>
|
||||
Status
|
||||
<Tooltip title="Sent system metrics in last 10 mins">
|
||||
<Info size="md" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
accessorFn: (row): boolean => row.active,
|
||||
width: { min: 150, default: 150 },
|
||||
enableSort: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const active = value as boolean;
|
||||
return (
|
||||
<Badge
|
||||
className={`${styles.statusTag} ${
|
||||
active ? styles.statusTagActive : styles.statusTagInactive
|
||||
}`}
|
||||
>
|
||||
{active ? 'ACTIVE' : 'INACTIVE'}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
header: (): React.ReactNode => (
|
||||
<div className={styles.columnHeaderRight}>CPU Usage</div>
|
||||
),
|
||||
accessorFn: (row): number => row.cpu,
|
||||
width: { min: 220 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpu = value as number;
|
||||
return (
|
||||
<div className={styles.progressContainer}>
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpu}
|
||||
entity={InfraMonitoringEntity.HOSTS}
|
||||
attribute="CPU metric"
|
||||
>
|
||||
<EntityProgressBar value={cpu} type="cpu" />
|
||||
</ValidateColumnValueWrapper>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
header: (): React.ReactNode => (
|
||||
<div className={`${styles.columnHeaderRight} ${styles.memoryUsageHeader}`}>
|
||||
Memory Usage
|
||||
<Tooltip title="Excluding cache memory">
|
||||
<Info size="md" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
accessorFn: (row): number => row.memory,
|
||||
width: { min: 220 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memory = value as number;
|
||||
return (
|
||||
<div className={styles.progressContainer}>
|
||||
<ValidateColumnValueWrapper
|
||||
value={memory}
|
||||
entity={InfraMonitoringEntity.HOSTS}
|
||||
attribute="memory metric"
|
||||
>
|
||||
<EntityProgressBar value={memory} type="memory" />
|
||||
</ValidateColumnValueWrapper>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'wait',
|
||||
header: (): React.ReactNode => (
|
||||
<div className={styles.columnHeaderRight}>IOWait</div>
|
||||
),
|
||||
accessorFn: (row): number => row.wait,
|
||||
width: { min: 100, default: 100 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const wait = value as number;
|
||||
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={wait}
|
||||
entity={InfraMonitoringEntity.HOSTS}
|
||||
attribute="IOWait metric"
|
||||
>
|
||||
<TanStackTable.Text>{`${Number((wait * 100).toFixed(1))}%`}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'load15',
|
||||
header: (): React.ReactNode => (
|
||||
<div className={styles.columnHeaderRight}>Load Avg</div>
|
||||
),
|
||||
accessorFn: (row): number => row.load15,
|
||||
width: { min: 100, default: 100 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const load15 = value as number;
|
||||
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={load15}
|
||||
entity={InfraMonitoringEntity.HOSTS}
|
||||
attribute="load average metric"
|
||||
>
|
||||
<TanStackTable.Text>{load15}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,68 +0,0 @@
|
||||
.entityGroupHeader {
|
||||
padding-left: var(--spacing-5);
|
||||
gap: var(--spacing-5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hostnameColumnHeader {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.statusHeader {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.columnHeaderRight {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.memoryUsageHeader {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.statusTag {
|
||||
width: fit-content;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.44px;
|
||||
text-transform: uppercase;
|
||||
border-radius: 50px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.statusTagActive {
|
||||
color: var(--Forest-500, #25e192);
|
||||
border: 1px solid rgba(37, 225, 146, 0.2);
|
||||
background: rgba(37, 225, 146, 0.1);
|
||||
}
|
||||
|
||||
.statusTagInactive {
|
||||
color: var(--Slate-50, #62687c);
|
||||
border: 1px solid rgba(98, 104, 124, 0.2);
|
||||
background: rgba(98, 104, 124, 0.1);
|
||||
}
|
||||
|
||||
.progressContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { HostListPayload } from 'api/infraMonitoring/getHostLists';
|
||||
import {
|
||||
FiltersType,
|
||||
IQuickFiltersConfig,
|
||||
} from 'components/QuickFilters/types';
|
||||
import { TriangleAlert } from '@signozhq/icons';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
const HOSTNAME_DOCS_URL =
|
||||
'https://signoz.io/docs/infrastructure-monitoring/hostmetrics/#host-name-is-blankempty';
|
||||
|
||||
export function HostnameCell({
|
||||
hostName,
|
||||
}: {
|
||||
hostName?: string | null;
|
||||
}): React.ReactElement {
|
||||
const isEmpty = !hostName || !hostName.trim();
|
||||
if (!isEmpty) {
|
||||
return <div className="hostname-column-value">{hostName}</div>;
|
||||
}
|
||||
return (
|
||||
<div className="hostname-cell-missing">
|
||||
<Typography.Text color="muted" className="hostname-cell-placeholder">
|
||||
-
|
||||
</Typography.Text>
|
||||
<Tooltip
|
||||
title={
|
||||
<div>
|
||||
Missing host.name metadata.
|
||||
<br />
|
||||
<a
|
||||
href={HOSTNAME_DOCS_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e): void => e.stopPropagation()}
|
||||
>
|
||||
Learn how to configure →
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
trigger={['hover', 'focus']}
|
||||
>
|
||||
<span
|
||||
className="hostname-cell-warning-icon"
|
||||
tabIndex={0}
|
||||
role="img"
|
||||
aria-label="Missing host.name metadata"
|
||||
onClick={(e): void => e.stopPropagation()}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TriangleAlert size={14} color={Color.BG_CHERRY_500} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const getHostListsQuery = (): HostListPayload => ({
|
||||
filters: {
|
||||
items: [],
|
||||
op: 'and',
|
||||
},
|
||||
groupBy: [],
|
||||
orderBy: { columnName: 'cpu', order: 'desc' },
|
||||
});
|
||||
|
||||
export const HostsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Host Name',
|
||||
attributeKey: {
|
||||
key: 'host_name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'system_cpu_load_average_15m',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'OS Type',
|
||||
attributeKey: {
|
||||
key: 'os_type',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'system_cpu_load_average_15m',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
];
|
||||
|
||||
export function getHostsQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const hostNameKey = dotMetricsEnabled ? 'host.name' : 'host_name';
|
||||
const osTypeKey = dotMetricsEnabled ? 'os.type' : 'os_type';
|
||||
const metricName = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.15m'
|
||||
: 'system_cpu_load_average_15m';
|
||||
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Host Name',
|
||||
attributeKey: {
|
||||
key: hostNameKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'OS Type',
|
||||
attributeKey: {
|
||||
key: osTypeKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
defaultOpen: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
ChevronsLeftRight,
|
||||
Compass,
|
||||
DraftingCompass,
|
||||
Package2,
|
||||
ScrollText,
|
||||
X,
|
||||
} from '@signozhq/icons';
|
||||
@@ -53,10 +54,12 @@ import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { InfraMonitoringEntity, VIEW_TYPES } from '../constants';
|
||||
import EntityContainers from '../EntityDetailsUtils/EntityContainers';
|
||||
import EntityEvents from '../EntityDetailsUtils/EntityEvents';
|
||||
import EntityLogs from '../EntityDetailsUtils/EntityLogs';
|
||||
import { K8S_ENTITY_LOGS_EXPRESSION_KEY } from '../EntityDetailsUtils/EntityLogs/hooks';
|
||||
import EntityMetrics from '../EntityDetailsUtils/EntityMetrics';
|
||||
import EntityProcesses from '../EntityDetailsUtils/EntityProcesses';
|
||||
import EntityTraces from '../EntityDetailsUtils/EntityTraces';
|
||||
import { K8S_ENTITY_TRACES_EXPRESSION_KEY } from '../EntityDetailsUtils/EntityTraces/hooks';
|
||||
import {
|
||||
@@ -117,6 +120,8 @@ export interface K8sBaseDetailsProps<T> {
|
||||
showLogs?: boolean;
|
||||
showTraces?: boolean;
|
||||
showEvents?: boolean;
|
||||
showContainers?: boolean;
|
||||
showProcesses?: boolean;
|
||||
};
|
||||
customTabs?: Array<{
|
||||
key: string;
|
||||
@@ -254,6 +259,8 @@ export default function K8sBaseDetails<T>({
|
||||
showLogs: true,
|
||||
showTraces: true,
|
||||
showEvents: true,
|
||||
showContainers: false,
|
||||
showProcesses: false,
|
||||
...tabsConfig,
|
||||
}),
|
||||
[tabsConfig],
|
||||
@@ -568,6 +575,32 @@ export default function K8sBaseDetails<T>({
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(tabVisibility.showContainers
|
||||
? [
|
||||
{
|
||||
value: VIEW_TYPES.CONTAINERS,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
<Package2 size={14} />
|
||||
Containers
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(tabVisibility.showProcesses
|
||||
? [
|
||||
{
|
||||
value: VIEW_TYPES.PROCESSES,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
<ChevronsLeftRight size={14} />
|
||||
Processes
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(customTabs?.map((tab) => ({
|
||||
value: tab.key,
|
||||
label: (
|
||||
@@ -647,6 +680,11 @@ export default function K8sBaseDetails<T>({
|
||||
initialExpression={eventsInitialExpression}
|
||||
/>
|
||||
)}
|
||||
{effectiveView === VIEW_TYPES.CONTAINERS &&
|
||||
tabVisibility.showContainers && <EntityContainers />}
|
||||
{effectiveView === VIEW_TYPES.PROCESSES && tabVisibility.showProcesses && (
|
||||
<EntityProcesses />
|
||||
)}
|
||||
{customTabs?.map((tab) =>
|
||||
selectedView === tab.key ? (
|
||||
<React.Fragment key={tab.key}>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import WaitlistFragment from 'components/HostMetricsDetail/WaitlistFragment/WaitlistFragment';
|
||||
|
||||
import broomUrl from '@/assets/Icons/broom.svg';
|
||||
import infraContainersUrl from '@/assets/Icons/infraContainers.svg';
|
||||
|
||||
import 'components/HostMetricsDetail/Containers/Containers.styles.scss';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
function EntityContainers(): JSX.Element {
|
||||
const { t } = useTranslation(['infraMonitoring']);
|
||||
|
||||
return (
|
||||
<Space direction="vertical" className="host-containers" size={24}>
|
||||
<div className="infra-container-card-container">
|
||||
<div className="dev-status-container">
|
||||
<div className="infra-container-card">
|
||||
<img
|
||||
src={infraContainersUrl}
|
||||
alt="infra-container"
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
|
||||
<Text className="infra-container-card-text">
|
||||
{t('containers_visualization_message')}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="infra-container-working-msg">
|
||||
<Space>
|
||||
<img src={broomUrl} alt="broom" width={24} height={24} />
|
||||
<Text className="infra-container-card-text">{t('working_message')}</Text>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<WaitlistFragment entityType="containers" />
|
||||
</div>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
export default EntityContainers;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import WaitlistFragment from 'components/HostMetricsDetail/WaitlistFragment/WaitlistFragment';
|
||||
|
||||
import broomUrl from '@/assets/Icons/broom.svg';
|
||||
import infraContainersUrl from '@/assets/Icons/infraContainers.svg';
|
||||
|
||||
import 'components/HostMetricsDetail/Processes/Processes.styles.scss';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
function EntityProcesses(): JSX.Element {
|
||||
const { t } = useTranslation(['infraMonitoring']);
|
||||
|
||||
return (
|
||||
<Space direction="vertical" className="host-processes" size={24}>
|
||||
<div className="infra-container-card-container">
|
||||
<div className="dev-status-container">
|
||||
<div className="infra-container-card">
|
||||
<img
|
||||
src={infraContainersUrl}
|
||||
alt="infra-container"
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
<Text className="infra-container-card-text">
|
||||
{t('processes_visualization_message')}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="infra-container-working-msg">
|
||||
<Space>
|
||||
<img src={broomUrl} alt="broom" width={24} height={24} />
|
||||
<Text className="infra-container-card-text">{t('working_message')}</Text>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<WaitlistFragment entityType="processes" />
|
||||
</div>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
export default EntityProcesses;
|
||||
@@ -1,98 +0,0 @@
|
||||
import { act, waitFor } from 'tests/test-utils';
|
||||
import { QueryRangePayloadV5 } from 'types/api/v5/queryRange';
|
||||
|
||||
import { renderEntityTraces, verifyQueryPayload } from './testUtils';
|
||||
import { mockQueryRangeV5WithTracesResponse } from '__tests__/query_range_v5.util';
|
||||
|
||||
describe('EntityTraces - Default Behavior', () => {
|
||||
let capturedPayloads: QueryRangePayloadV5[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
capturedPayloads = [];
|
||||
mockQueryRangeV5WithTracesResponse({
|
||||
onReceiveRequest: async (req) => {
|
||||
const body = (await req.json()) as QueryRangePayloadV5;
|
||||
capturedPayloads.push(body);
|
||||
return {};
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch traces using V5 API on initial render', async () => {
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
verifyQueryPayload({
|
||||
payload: capturedPayloads[0],
|
||||
expectedOffset: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass time range to API (converted to milliseconds)', async () => {
|
||||
const timeRange = { startTime: 1000, endTime: 2000 };
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces({ timeRange });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
verifyQueryPayload({
|
||||
payload: capturedPayloads[0],
|
||||
expectedTimeRange: {
|
||||
start: timeRange.startTime * 1000,
|
||||
end: timeRange.endTime * 1000,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should order results by timestamp desc', async () => {
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
const spec = capturedPayloads[0].compositeQuery.queries[0]?.spec as {
|
||||
order?: Array<{ key: { name: string }; direction: string }>;
|
||||
};
|
||||
const timestampOrder = spec.order?.find((o) => o.key.name === 'timestamp');
|
||||
|
||||
expect(timestampOrder?.direction).toBe('desc');
|
||||
});
|
||||
|
||||
it('should use TRACES signal type', async () => {
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
const query = capturedPayloads[0].compositeQuery.queries[0];
|
||||
const spec = query?.spec as { signal?: string };
|
||||
expect(spec?.signal).toBe('traces');
|
||||
});
|
||||
|
||||
it('should use raw request type for traces list view', async () => {
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
expect(capturedPayloads[0].requestType).toBe('raw');
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
import { act, screen } from 'tests/test-utils';
|
||||
|
||||
import { renderEntityTraces } from './testUtils';
|
||||
import { mockQueryRangeV5WithEmptyTraces } from '__tests__/query_range_v5.util';
|
||||
|
||||
describe('EntityTraces - Empty State', () => {
|
||||
it('should show empty state when no traces returned', async () => {
|
||||
mockQueryRangeV5WithEmptyTraces();
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(screen.findByText(/No data yet/i)).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show table when traces are empty', async () => {
|
||||
mockQueryRangeV5WithEmptyTraces();
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(screen.findByText(/No data yet/i)).resolves.toBeInTheDocument();
|
||||
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show pagination controls when traces are empty', async () => {
|
||||
mockQueryRangeV5WithEmptyTraces();
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(screen.findByText(/No data yet/i)).resolves.toBeInTheDocument();
|
||||
expect(screen.queryByText(/1 - 10/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
import { act, screen } from 'tests/test-utils';
|
||||
|
||||
import { renderEntityTraces } from './testUtils';
|
||||
import {
|
||||
mockQueryRangeV5WithError,
|
||||
mockQueryRangeV5WithKeyNotFoundError,
|
||||
} from '__tests__/query_range_v5.util';
|
||||
|
||||
describe('EntityTraces - Error State', () => {
|
||||
it('should show error state on API error', async () => {
|
||||
mockQueryRangeV5WithError('Internal server error', 500);
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(
|
||||
screen.findByText(/Something went wrong/i),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show empty state for key not found error', async () => {
|
||||
mockQueryRangeV5WithKeyNotFoundError();
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(screen.findByText(/No data yet/i)).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show table on API error', async () => {
|
||||
mockQueryRangeV5WithError('Internal server error', 500);
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(
|
||||
screen.findByText(/Something went wrong/i),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,55 +0,0 @@
|
||||
import { act, waitFor } from 'tests/test-utils';
|
||||
import { QueryRangePayloadV5 } from 'types/api/v5/queryRange';
|
||||
|
||||
import { renderEntityTraces } from './testUtils';
|
||||
import { mockQueryRangeV5WithTracesResponse } from '__tests__/query_range_v5.util';
|
||||
|
||||
describe('EntityTraces - Expression', () => {
|
||||
let capturedPayloads: QueryRangePayloadV5[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
capturedPayloads = [];
|
||||
mockQueryRangeV5WithTracesResponse({
|
||||
onReceiveRequest: async (req) => {
|
||||
const body = (await req.json()) as QueryRangePayloadV5;
|
||||
capturedPayloads.push(body);
|
||||
return {};
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should include expression in query filter', async () => {
|
||||
const expression = 'k8s.pod.name = "my-pod"';
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces({ expression });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
const spec = capturedPayloads[0].compositeQuery.queries[0]?.spec as {
|
||||
filter?: { expression: string };
|
||||
};
|
||||
expect(spec.filter?.expression).toContain('k8s.pod.name');
|
||||
});
|
||||
|
||||
it('should include complex expression with multiple conditions', async () => {
|
||||
const expression = 'k8s.pod.name = "my-pod" AND service.name = "api"';
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces({ expression });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
const spec = capturedPayloads[0].compositeQuery.queries[0]?.spec as {
|
||||
filter?: { expression: string };
|
||||
};
|
||||
expect(spec.filter?.expression).toContain('k8s.pod.name');
|
||||
expect(spec.filter?.expression).toContain('service.name');
|
||||
});
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
import { act, screen, waitFor } from 'tests/test-utils';
|
||||
|
||||
import { renderEntityTraces } from './testUtils';
|
||||
import { mockQueryRangeV5WithTracesResponse } from '__tests__/query_range_v5.util';
|
||||
|
||||
describe('EntityTraces - Loading State', () => {
|
||||
it('should show loading state while fetching', async () => {
|
||||
mockQueryRangeV5WithTracesResponse({ delay: 1000 });
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(
|
||||
screen.findByText(/pending_data_placeholder/i),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should hide loading state after data loads', async () => {
|
||||
mockQueryRangeV5WithTracesResponse({ delay: 100 });
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(
|
||||
screen.findByText(/pending_data_placeholder/i),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(
|
||||
screen.queryByText(/pending_data_placeholder/i),
|
||||
).not.toBeInTheDocument();
|
||||
},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,96 +0,0 @@
|
||||
import { act, waitFor } from 'tests/test-utils';
|
||||
import { QueryRangePayloadV5 } from 'types/api/v5/queryRange';
|
||||
|
||||
import { renderEntityTraces, verifyQueryPayload } from './testUtils';
|
||||
import { mockQueryRangeV5WithTracesResponse } from '__tests__/query_range_v5.util';
|
||||
|
||||
describe('EntityTraces - Pagination', () => {
|
||||
let capturedPayloads: QueryRangePayloadV5[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
capturedPayloads = [];
|
||||
mockQueryRangeV5WithTracesResponse({
|
||||
onReceiveRequest: async (req) => {
|
||||
const body = (await req.json()) as QueryRangePayloadV5;
|
||||
capturedPayloads.push(body);
|
||||
return {};
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should use default limit when no pagination provided', async () => {
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
const spec = capturedPayloads[0].compositeQuery.queries[0]?.spec as {
|
||||
limit?: number;
|
||||
};
|
||||
expect(spec.limit).toBe(10);
|
||||
});
|
||||
|
||||
it('should use custom offset from pagination param', async () => {
|
||||
act(() => {
|
||||
renderEntityTraces({ pagination: { offset: 20, limit: 10 } });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
verifyQueryPayload({
|
||||
payload: capturedPayloads[0],
|
||||
expectedOffset: 20,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use custom limit from pagination param', async () => {
|
||||
act(() => {
|
||||
renderEntityTraces({ pagination: { offset: 0, limit: 50 } });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
verifyQueryPayload({
|
||||
payload: capturedPayloads[0],
|
||||
expectedLimit: 50,
|
||||
});
|
||||
});
|
||||
|
||||
it('should default offset to 0', async () => {
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
verifyQueryPayload({
|
||||
payload: capturedPayloads[0],
|
||||
expectedOffset: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should combine offset and limit correctly', async () => {
|
||||
act(() => {
|
||||
renderEntityTraces({ pagination: { offset: 30, limit: 20 } });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayloads).toHaveLength(1);
|
||||
});
|
||||
|
||||
verifyQueryPayload({
|
||||
payload: capturedPayloads[0],
|
||||
expectedOffset: 30,
|
||||
expectedLimit: 20,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,185 +0,0 @@
|
||||
import { act, screen, within } from 'tests/test-utils';
|
||||
|
||||
import { renderEntityTraces } from './testUtils';
|
||||
import { mockQueryRangeV5WithTracesResponse } from '__tests__/query_range_v5.util';
|
||||
|
||||
// Trace list columns are hidden below the antd `md` breakpoint. The global
|
||||
// matchMedia mock reports `matches: false`, which drops every column, so make
|
||||
// all breakpoints match for these rendering tests.
|
||||
jest.spyOn(window, 'matchMedia').mockImplementation(
|
||||
(query: string) =>
|
||||
({
|
||||
matches: true,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
}) as unknown as MediaQueryList,
|
||||
);
|
||||
|
||||
describe('EntityTraces - Table Rendering', () => {
|
||||
it('should render table with all column headers', async () => {
|
||||
mockQueryRangeV5WithTracesResponse({ pageSize: 3 });
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(screen.findByText('Service Name')).resolves.toBeInTheDocument();
|
||||
|
||||
const headerTexts = within(screen.getByRole('table'))
|
||||
.getAllByRole('columnheader')
|
||||
.map((header) => header.textContent);
|
||||
|
||||
expect(headerTexts).toStrictEqual(
|
||||
expect.arrayContaining([
|
||||
'Timestamp',
|
||||
'Service Name',
|
||||
'Name',
|
||||
'Duration',
|
||||
'HTTP Method',
|
||||
'Status Code',
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should render trace values in table cells', async () => {
|
||||
mockQueryRangeV5WithTracesResponse({
|
||||
customTraces: [
|
||||
{
|
||||
serviceName: 'checkout-service',
|
||||
name: 'GET /api/checkout',
|
||||
durationNano: 5000000,
|
||||
httpMethod: 'GET',
|
||||
responseStatusCode: '200',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(screen.findByTestId('serviceName')).resolves.toHaveTextContent(
|
||||
'checkout-service',
|
||||
);
|
||||
expect(screen.getByTestId('name')).toHaveTextContent('GET /api/checkout');
|
||||
expect(screen.getByTestId('durationNano')).toHaveTextContent('5.00ms');
|
||||
expect(screen.getByTestId('httpMethod')).toHaveTextContent('GET');
|
||||
expect(screen.getByTestId('responseStatusCode')).toHaveTextContent('200');
|
||||
});
|
||||
|
||||
it('should render http method as robin colored outline badge', async () => {
|
||||
mockQueryRangeV5WithTracesResponse({
|
||||
customTraces: [{ httpMethod: 'POST', responseStatusCode: '200' }],
|
||||
});
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
const badge = await screen.findByTestId('httpMethod');
|
||||
expect(badge).toHaveTextContent('POST');
|
||||
expect(badge).toHaveAttribute('data-color', 'robin');
|
||||
expect(badge).toHaveAttribute('data-variant', 'outline');
|
||||
});
|
||||
|
||||
it('should render N/A when http method is empty', async () => {
|
||||
mockQueryRangeV5WithTracesResponse({
|
||||
customTraces: [{ httpMethod: '', responseStatusCode: '200' }],
|
||||
});
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(screen.findByText('N/A')).resolves.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('httpMethod')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render status code badge color per status range', async () => {
|
||||
mockQueryRangeV5WithTracesResponse({
|
||||
customTraces: [
|
||||
{ responseStatusCode: '200' },
|
||||
{ responseStatusCode: '302' },
|
||||
{ responseStatusCode: '404' },
|
||||
{ responseStatusCode: '500' },
|
||||
{ responseStatusCode: '100' },
|
||||
],
|
||||
});
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
const badges = await screen.findAllByTestId('responseStatusCode');
|
||||
const badgeColors = badges.map((badge) => badge.getAttribute('data-color'));
|
||||
|
||||
expect(badgeColors).toStrictEqual([
|
||||
'forest', // 2xx -> success
|
||||
'robin', // 3xx -> redirect
|
||||
'amber', // 4xx -> client error
|
||||
'cherry', // 5xx -> server error
|
||||
'vanilla', // 1xx -> informational
|
||||
]);
|
||||
});
|
||||
|
||||
it('should render non-numeric status code as plain text without badge', async () => {
|
||||
mockQueryRangeV5WithTracesResponse({
|
||||
customTraces: [{ responseStatusCode: 'unknown' }],
|
||||
});
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(screen.findByText('unknown')).resolves.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('responseStatusCode')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should link cells to trace details page', async () => {
|
||||
mockQueryRangeV5WithTracesResponse({
|
||||
customTraces: [{ serviceName: 'frontend' }],
|
||||
});
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
await expect(screen.findByTestId('serviceName')).resolves.toBeInTheDocument();
|
||||
|
||||
const links = within(screen.getByRole('table')).getAllByRole('link');
|
||||
|
||||
expect(links.length).toBeGreaterThan(0);
|
||||
links.forEach((link) => {
|
||||
expect(link).toHaveAttribute(
|
||||
'href',
|
||||
expect.stringContaining('/trace/trace-id-0'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render one row per trace', async () => {
|
||||
mockQueryRangeV5WithTracesResponse({
|
||||
customTraces: [
|
||||
{ serviceName: 'frontend' },
|
||||
{ serviceName: 'backend' },
|
||||
{ serviceName: 'database' },
|
||||
],
|
||||
});
|
||||
|
||||
act(() => {
|
||||
renderEntityTraces();
|
||||
});
|
||||
|
||||
const serviceCells = await screen.findAllByTestId('serviceName');
|
||||
expect(serviceCells.map((cell) => cell.textContent)).toStrictEqual([
|
||||
'frontend',
|
||||
'backend',
|
||||
'database',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,13 @@
|
||||
import { mockQueryRangeV5WithEventsResponse } from '__tests__/query_range_v5.util';
|
||||
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants';
|
||||
import { mockQueryRangeV5WithLogsResponse } from '__tests__/query_range_v5.util';
|
||||
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8s/constants';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { act, render, screen, waitFor } from 'tests/test-utils';
|
||||
import { QueryRangePayloadV5 } from 'types/api/v5/queryRange';
|
||||
|
||||
import EntityEvents from '../EntityEvents';
|
||||
import { K8S_ENTITY_EVENTS_EXPRESSION_KEY } from '../hooks';
|
||||
import EntityTraces from '../EntityTraces';
|
||||
import { K8S_ENTITY_TRACES_EXPRESSION_KEY } from '../hooks';
|
||||
|
||||
function verifyEntityEventsV5Request({
|
||||
function verifyEntityTracesV5Request({
|
||||
payload,
|
||||
expectedOffset,
|
||||
initialTimeRange,
|
||||
@@ -48,12 +48,12 @@ jest.mock('container/TopNav/DateTimeSelectionV2/index.tsx', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
describe('EntityEvents', () => {
|
||||
describe('EntityTraces', () => {
|
||||
let capturedQueryRangePayloads: QueryRangePayloadV5[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
capturedQueryRangePayloads = [];
|
||||
mockQueryRangeV5WithEventsResponse({
|
||||
mockQueryRangeV5WithLogsResponse({
|
||||
onReceiveRequest: async (req) => {
|
||||
const body = (await req.json()) as QueryRangePayloadV5;
|
||||
capturedQueryRangePayloads.push(body);
|
||||
@@ -62,13 +62,13 @@ describe('EntityEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should use V5 API for fetching events', async () => {
|
||||
it('should use V5 API for fetching traces', async () => {
|
||||
act(() => {
|
||||
render(
|
||||
<NuqsTestingAdapter
|
||||
searchParams={`${K8S_ENTITY_EVENTS_EXPRESSION_KEY}=k8s.pod.name+%3D+%22x%22`}
|
||||
searchParams={`${K8S_ENTITY_TRACES_EXPRESSION_KEY}=k8s.pod.name+%3D+%22x%22`}
|
||||
>
|
||||
<EntityEvents
|
||||
<EntityTraces
|
||||
timeRange={{ startTime: 1, endTime: 2 }}
|
||||
isModalTimeSelection={false}
|
||||
handleTimeChange={jest.fn()}
|
||||
@@ -92,7 +92,7 @@ describe('EntityEvents', () => {
|
||||
});
|
||||
|
||||
const firstPayload = capturedQueryRangePayloads[0];
|
||||
verifyEntityEventsV5Request({
|
||||
verifyEntityTracesV5Request({
|
||||
payload: firstPayload,
|
||||
expectedOffset: 0,
|
||||
});
|
||||
@@ -1,121 +0,0 @@
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8s/constants';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { render, RenderResult } from 'tests/test-utils';
|
||||
import { QueryRangePayloadV5 } from 'types/api/v5/queryRange';
|
||||
|
||||
import EntityTraces from '../EntityTraces';
|
||||
import { K8S_ENTITY_TRACES_EXPRESSION_KEY } from '../hooks';
|
||||
|
||||
// QuerySearch fires autocomplete requests on mount; without handlers MSW
|
||||
// passes them through to the real network and the resulting AxiosError fails
|
||||
// whichever test happens to be running.
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.get(`${ENVIRONMENT.baseURL}/api/v1/fields/keys`, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({ status: 'success', data: { keys: {}, complete: true } }),
|
||||
),
|
||||
),
|
||||
rest.get(`${ENVIRONMENT.baseURL}/api/v1/fields/values`, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({ status: 'success', data: { values: {}, complete: true } }),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
export interface RenderEntityTracesOptions {
|
||||
expression?: string;
|
||||
timeRange?: { startTime: number; endTime: number };
|
||||
category?: InfraMonitoringEntity;
|
||||
selectedInterval?: '5m' | '15m' | '30m' | '1h';
|
||||
pagination?: { offset: number; limit: number };
|
||||
}
|
||||
|
||||
export function renderEntityTraces({
|
||||
expression = 'k8s.pod.name = "test-pod"',
|
||||
timeRange = { startTime: 1, endTime: 2 },
|
||||
category = InfraMonitoringEntity.PODS,
|
||||
selectedInterval = '5m',
|
||||
pagination,
|
||||
}: RenderEntityTracesOptions = {}): RenderResult {
|
||||
const encodedExpression = encodeURIComponent(expression);
|
||||
let searchParams = `${K8S_ENTITY_TRACES_EXPRESSION_KEY}=${encodedExpression}`;
|
||||
|
||||
if (pagination) {
|
||||
const paginationStr = encodeURIComponent(JSON.stringify(pagination));
|
||||
searchParams += `&pagination=${paginationStr}`;
|
||||
}
|
||||
|
||||
return render(
|
||||
<NuqsTestingAdapter searchParams={searchParams}>
|
||||
<EntityTraces
|
||||
timeRange={timeRange}
|
||||
isModalTimeSelection={false}
|
||||
handleTimeChange={jest.fn()}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="test"
|
||||
category={category}
|
||||
initialExpression={expression}
|
||||
/>
|
||||
</NuqsTestingAdapter>,
|
||||
);
|
||||
}
|
||||
|
||||
export function verifyQueryPayload({
|
||||
payload,
|
||||
expectedOffset,
|
||||
expectedLimit,
|
||||
expectedTimeRange,
|
||||
}: {
|
||||
payload: QueryRangePayloadV5;
|
||||
expectedOffset?: number;
|
||||
expectedLimit?: number;
|
||||
expectedTimeRange?: { start: number; end: number };
|
||||
}): void {
|
||||
const spec = payload.compositeQuery.queries[0]?.spec as {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
order?: Array<{ key: { name: string }; direction: string }>;
|
||||
};
|
||||
|
||||
if (expectedOffset !== undefined) {
|
||||
expect(spec.offset).toBe(expectedOffset);
|
||||
}
|
||||
|
||||
if (expectedLimit !== undefined) {
|
||||
expect(spec.limit).toBe(expectedLimit);
|
||||
}
|
||||
|
||||
if (expectedTimeRange) {
|
||||
expect(payload.start).toBe(expectedTimeRange.start);
|
||||
expect(payload.end).toBe(expectedTimeRange.end);
|
||||
}
|
||||
|
||||
const orderKeys = spec.order?.map((o) => o.key.name) ?? [];
|
||||
expect(orderKeys).toContain('timestamp');
|
||||
}
|
||||
|
||||
jest.mock('container/TopNav/DateTimeSelectionV2/index.tsx', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
onTimeChange,
|
||||
}: {
|
||||
onTimeChange?: (interval: string, dateTimeRange?: [number, number]) => void;
|
||||
}): JSX.Element => (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="mock-datetime-selection"
|
||||
onClick={(): void => {
|
||||
onTimeChange?.('5m');
|
||||
}}
|
||||
>
|
||||
Select Time
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
@@ -1,7 +0,0 @@
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cellText {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { TableColumnsType as ColumnsType } from 'antd';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import HttpStatusBadge from 'components/HttpStatusBadge/HttpStatusBadge';
|
||||
import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util';
|
||||
import {
|
||||
BlockLink,
|
||||
@@ -10,7 +9,6 @@ import {
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { FormatTimezoneAdjustedTimestamp } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
|
||||
import styles from './traceListColumns.module.scss';
|
||||
|
||||
const keyToLabelMap: Record<string, string> = {
|
||||
timestamp: 'Timestamp',
|
||||
@@ -50,9 +48,8 @@ const getValueForKey = (data: Record<string, any>, key: string): any => {
|
||||
const aliases = keyAliases[primaryKey];
|
||||
if (aliases) {
|
||||
for (const alias of aliases) {
|
||||
const aliasedValue = data[alias] || data.resource?.[alias];
|
||||
if (aliasedValue !== undefined) {
|
||||
return aliasedValue;
|
||||
if (data[alias] !== undefined) {
|
||||
return data[alias];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +78,7 @@ export const getTraceListColumns = (
|
||||
|
||||
return (
|
||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
||||
<Typography.Text className={styles.cellText}>{date}</Typography.Text>
|
||||
<Typography.Text>{date}</Typography.Text>
|
||||
</BlockLink>
|
||||
);
|
||||
}
|
||||
@@ -89,81 +86,34 @@ export const getTraceListColumns = (
|
||||
if (value === '') {
|
||||
return (
|
||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
||||
<Typography data-testid={key} className={styles.cellText}>
|
||||
N/A
|
||||
</Typography>
|
||||
<Typography data-testid={key}>N/A</Typography>
|
||||
</BlockLink>
|
||||
);
|
||||
}
|
||||
|
||||
if (primaryKey === 'httpMethod') {
|
||||
const httpMethod = getValueForKey(itemData, key);
|
||||
|
||||
if (!httpMethod) {
|
||||
return (
|
||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
||||
<Typography className={styles.cellText}>N/A</Typography>
|
||||
</BlockLink>
|
||||
);
|
||||
}
|
||||
|
||||
if (primaryKey === 'httpMethod' || primaryKey === 'responseStatusCode') {
|
||||
return (
|
||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
||||
<Badge
|
||||
data-testid={key}
|
||||
color="robin"
|
||||
variant="outline"
|
||||
className={styles.pointer}
|
||||
>
|
||||
{httpMethod}
|
||||
<Badge data-testid={key} color="sakura">
|
||||
{getValueForKey(itemData, key)}
|
||||
</Badge>
|
||||
</BlockLink>
|
||||
);
|
||||
}
|
||||
|
||||
if (primaryKey === 'responseStatusCode') {
|
||||
const statusCode = getValueForKey(itemData, key);
|
||||
const numericCode = Number(statusCode);
|
||||
const isValidCode = !Number.isNaN(numericCode) && numericCode > 0;
|
||||
|
||||
if (!isValidCode) {
|
||||
return (
|
||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
||||
<Typography className={styles.cellText}>
|
||||
{numericCode === 0 || !statusCode ? 'N/A' : statusCode}
|
||||
</Typography>
|
||||
</BlockLink>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
||||
<HttpStatusBadge
|
||||
statusCode={statusCode}
|
||||
testId={key}
|
||||
className={styles.pointer}
|
||||
/>
|
||||
</BlockLink>
|
||||
);
|
||||
}
|
||||
|
||||
if (primaryKey === 'durationNano') {
|
||||
const durationNano = getValueForKey(itemData, key);
|
||||
|
||||
return (
|
||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
||||
<Typography data-testid={key} className={styles.cellText}>
|
||||
{getMs(durationNano)}ms
|
||||
</Typography>
|
||||
<Typography data-testid={key}>{getMs(durationNano)}ms</Typography>
|
||||
</BlockLink>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
||||
<Typography data-testid={key} className={styles.cellText}>
|
||||
{getValueForKey(itemData, key)}
|
||||
</Typography>
|
||||
<Typography data-testid={key}>{getValueForKey(itemData, key)}</Typography>
|
||||
</BlockLink>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -44,15 +44,40 @@
|
||||
}
|
||||
|
||||
.quickFiltersContainer {
|
||||
// Width is owned by ResizableBox (inline style). No overflow here: the panel
|
||||
// must not scroll (QuickFilters scrolls its own list internally) — an
|
||||
// overflow on the root would also clip the edge-mounted resize grip.
|
||||
flex-shrink: 0;
|
||||
width: 280px;
|
||||
min-width: 280px;
|
||||
border-right: 1px solid var(--l1-border);
|
||||
overflow-y: auto;
|
||||
|
||||
:global(.resizable-box__content) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.1rem;
|
||||
height: 0.1rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: colox-mix(var(--accent-primary), var(--bg-vanilla-100), 20%);
|
||||
}
|
||||
|
||||
:global(.ant-collapse-header) {
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
:global(.ant-collapse-content-box) {
|
||||
padding: 0 !important;
|
||||
padding-block: 0 !important;
|
||||
|
||||
:global(.quick-filters .checkbox-filter) {
|
||||
padding-left: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.quick-filters) {
|
||||
@@ -86,6 +111,22 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.quickFiltersCategoryLabel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.quickFiltersCategoryLabelIcon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.quickFiltersCategoryLabelContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.listContainer {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
@@ -117,118 +158,5 @@
|
||||
}
|
||||
|
||||
.listContainerFiltersVisible {
|
||||
// Width is driven by flex now that the filters panel is resizable; the list
|
||||
// fills whatever space the (variable-width) panel leaves.
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.categorySelectorSection {
|
||||
padding: var(--spacing-4);
|
||||
}
|
||||
|
||||
.sectionHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
margin-bottom: var(--spacing-4);
|
||||
|
||||
&[data-type='filter'] {
|
||||
padding: 0 var(--spacing-4);
|
||||
}
|
||||
}
|
||||
|
||||
.sectionLabel {
|
||||
font-size: var(--periscope-font-size-small);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--l3-foreground);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sectionLine {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--l1-border);
|
||||
}
|
||||
|
||||
.categoryCard {
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
border-radius: var(--spacing-3);
|
||||
padding: var(--spacing-2);
|
||||
}
|
||||
|
||||
.categoryList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-1);
|
||||
}
|
||||
|
||||
.categoryItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-5);
|
||||
padding: var(--spacing-3) var(--spacing-4);
|
||||
border: none;
|
||||
border-radius: var(--spacing-2);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
color: var(--l2-foreground);
|
||||
font-size: var(--periscope-font-size-base);
|
||||
--typography-color: var(--l1-foreground);
|
||||
|
||||
&:hover:not(.categoryItemSelected) {
|
||||
background: var(--l2-background-hover);
|
||||
}
|
||||
|
||||
svg {
|
||||
color: var(--l3-foreground);
|
||||
flex-shrink: 0;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
&.categoryItemSelected {
|
||||
background: var(--accent-primary);
|
||||
color: var(--l1-background);
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
background: var(--accent-primary-hover, var(--accent-primary));
|
||||
}
|
||||
|
||||
svg {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quickFiltersSection {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
|
||||
:global(.quick-filters-container) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.1rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
max-width: calc(100% - 280px);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { Button, CollapseProps } from 'antd';
|
||||
import { Collapse, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import {
|
||||
@@ -23,8 +23,6 @@ import {
|
||||
Workflow,
|
||||
} from '@signozhq/icons';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import { ResizableBox } from 'periscope/components/ResizableBox';
|
||||
import usePanelWidth from 'periscope/components/ResizableBox/usePanelWidth';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
@@ -59,23 +57,9 @@ import K8sVolumesList from './Volumes/K8sVolumesList';
|
||||
|
||||
import styles from './InfraMonitoringK8s.module.scss';
|
||||
|
||||
const QUICK_FILTERS_DEFAULT_WIDTH = 280;
|
||||
const QUICK_FILTERS_MIN_WIDTH = 240;
|
||||
const QUICK_FILTERS_MAX_WIDTH = 500;
|
||||
|
||||
export default function InfraMonitoringK8s(): JSX.Element {
|
||||
const [showFilters, setShowFilters] = useState(true);
|
||||
|
||||
const {
|
||||
initialWidth: quickFiltersInitialWidth,
|
||||
persistWidth: persistQuickFiltersWidth,
|
||||
} = usePanelWidth({
|
||||
storageKey: LOCALSTORAGE.QUICK_FILTERS_WIDTH_INFRA,
|
||||
defaultWidth: QUICK_FILTERS_DEFAULT_WIDTH,
|
||||
minWidth: QUICK_FILTERS_MIN_WIDTH,
|
||||
maxWidth: QUICK_FILTERS_MAX_WIDTH,
|
||||
});
|
||||
|
||||
const [selectedCategory, setSelectedCategory] = useInfraMonitoringCategory();
|
||||
const [urlFilters, setUrlFilters] = useInfraMonitoringFiltersK8s();
|
||||
const [, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
@@ -129,74 +113,192 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
});
|
||||
};
|
||||
|
||||
const categories = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: K8sCategories.PODS,
|
||||
label: 'Pods',
|
||||
icon: <Container size={14} />,
|
||||
config: GetPodsQuickFiltersConfig(dotMetricsEnabled),
|
||||
},
|
||||
{
|
||||
key: K8sCategories.NODES,
|
||||
label: 'Nodes',
|
||||
icon: <Workflow size={14} />,
|
||||
config: GetNodesQuickFiltersConfig(dotMetricsEnabled),
|
||||
},
|
||||
{
|
||||
key: K8sCategories.NAMESPACES,
|
||||
label: 'Namespaces',
|
||||
icon: <FilePenLine size={14} />,
|
||||
config: GetNamespaceQuickFiltersConfig(dotMetricsEnabled),
|
||||
},
|
||||
{
|
||||
key: K8sCategories.CLUSTERS,
|
||||
label: 'Clusters',
|
||||
icon: <Boxes size={14} />,
|
||||
config: GetClustersQuickFiltersConfig(dotMetricsEnabled),
|
||||
},
|
||||
{
|
||||
key: K8sCategories.DEPLOYMENTS,
|
||||
label: 'Deployments',
|
||||
icon: <Computer size={14} />,
|
||||
config: GetDeploymentsQuickFiltersConfig(dotMetricsEnabled),
|
||||
},
|
||||
{
|
||||
key: K8sCategories.JOBS,
|
||||
label: 'Jobs',
|
||||
icon: <Bolt size={14} />,
|
||||
config: GetJobsQuickFiltersConfig(dotMetricsEnabled),
|
||||
},
|
||||
{
|
||||
key: K8sCategories.DAEMONSETS,
|
||||
label: 'DaemonSets',
|
||||
icon: <Group size={14} />,
|
||||
config: GetDaemonsetsQuickFiltersConfig(dotMetricsEnabled),
|
||||
},
|
||||
{
|
||||
key: K8sCategories.STATEFULSETS,
|
||||
label: 'StatefulSets',
|
||||
icon: <ArrowUpDown size={14} />,
|
||||
config: GetStatefulsetsQuickFiltersConfig(dotMetricsEnabled),
|
||||
},
|
||||
{
|
||||
key: K8sCategories.VOLUMES,
|
||||
label: 'Volumes',
|
||||
icon: <HardDrive size={14} />,
|
||||
config: GetVolumesQuickFiltersConfig(dotMetricsEnabled),
|
||||
},
|
||||
],
|
||||
[dotMetricsEnabled],
|
||||
const renderCategoryLabel = (
|
||||
icon: JSX.Element,
|
||||
label: string,
|
||||
): JSX.Element => (
|
||||
<div className={styles.quickFiltersCategoryLabel}>
|
||||
<div className={styles.quickFiltersCategoryLabelContainer}>
|
||||
{icon}
|
||||
<Typography.Text>{label}</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const selectedCategoryConfig = useMemo(
|
||||
() => categories.find((cat) => cat.key === selectedCategory)?.config,
|
||||
[categories, selectedCategory],
|
||||
);
|
||||
const items: CollapseProps['items'] = [
|
||||
{
|
||||
label: renderCategoryLabel(
|
||||
<Container size={14} className={styles.quickFiltersCategoryLabelIcon} />,
|
||||
'Pods',
|
||||
),
|
||||
key: K8sCategories.PODS,
|
||||
showArrow: false,
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetPodsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: renderCategoryLabel(
|
||||
<Workflow size={14} className={styles.quickFiltersCategoryLabelIcon} />,
|
||||
'Nodes',
|
||||
),
|
||||
key: K8sCategories.NODES,
|
||||
showArrow: false,
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetNodesQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: renderCategoryLabel(
|
||||
<FilePenLine size={14} className={styles.quickFiltersCategoryLabelIcon} />,
|
||||
'Namespaces',
|
||||
),
|
||||
key: K8sCategories.NAMESPACES,
|
||||
showArrow: false,
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetNamespaceQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: renderCategoryLabel(
|
||||
<Boxes size={14} className={styles.quickFiltersCategoryLabelIcon} />,
|
||||
'Clusters',
|
||||
),
|
||||
key: K8sCategories.CLUSTERS,
|
||||
showArrow: false,
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetClustersQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: renderCategoryLabel(
|
||||
<Computer size={14} className={styles.quickFiltersCategoryLabelIcon} />,
|
||||
'Deployments',
|
||||
),
|
||||
key: K8sCategories.DEPLOYMENTS,
|
||||
showArrow: false,
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetDeploymentsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: renderCategoryLabel(
|
||||
<Bolt size={14} className={styles.quickFiltersCategoryLabelIcon} />,
|
||||
'Jobs',
|
||||
),
|
||||
key: K8sCategories.JOBS,
|
||||
showArrow: false,
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetJobsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: renderCategoryLabel(
|
||||
<Group size={14} className={styles.quickFiltersCategoryLabelIcon} />,
|
||||
'DaemonSets',
|
||||
),
|
||||
key: K8sCategories.DAEMONSETS,
|
||||
showArrow: false,
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetDaemonsetsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: renderCategoryLabel(
|
||||
<ArrowUpDown size={14} className={styles.quickFiltersCategoryLabelIcon} />,
|
||||
'StatefulSets',
|
||||
),
|
||||
key: K8sCategories.STATEFULSETS,
|
||||
showArrow: false,
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetStatefulsetsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: renderCategoryLabel(
|
||||
<HardDrive size={14} className={styles.quickFiltersCategoryLabelIcon} />,
|
||||
'Volumes',
|
||||
),
|
||||
key: K8sCategories.VOLUMES,
|
||||
showArrow: false,
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetVolumesQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
// TODO: Enable once we have implemented containers.
|
||||
// {
|
||||
// label: (
|
||||
// <div className="k8s-quick-filters-category-label">
|
||||
// <div className="k8s-quick-filters-category-label-container">
|
||||
// <PackageOpen
|
||||
// size={14}
|
||||
// className="k8s-quick-filters-category-label-icon"
|
||||
// />
|
||||
// <Typography.Text>Containers</Typography.Text>
|
||||
// </div>
|
||||
// </div>
|
||||
// ),
|
||||
// key: K8sCategories.CONTAINERS,
|
||||
// showArrow: false,
|
||||
// children: (
|
||||
// <QuickFilters
|
||||
// source={QuickFiltersSource.INFRA_MONITORING}
|
||||
// config={ContainersQuickFiltersConfig}
|
||||
// handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
// onFilterChange={handleFilterChange}
|
||||
// />
|
||||
// ),
|
||||
// },
|
||||
];
|
||||
|
||||
const handleCategorySelect = (key: string): void => {
|
||||
if (key !== selectedCategory) {
|
||||
setSelectedCategory(key);
|
||||
const handleCategoryChange = (key: string | string[]): void => {
|
||||
if (Array.isArray(key) && key.length > 0) {
|
||||
setSelectedCategory(key[0] as string);
|
||||
// Reset filters
|
||||
setUrlFilters(null);
|
||||
setOrderBy(null);
|
||||
@@ -229,71 +331,28 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
<div className={styles.infraMonitoringContainer}>
|
||||
<div className={styles.infraContentRow}>
|
||||
{showFilters && (
|
||||
<ResizableBox
|
||||
handle="right"
|
||||
defaultWidth={QUICK_FILTERS_DEFAULT_WIDTH}
|
||||
initialWidth={quickFiltersInitialWidth}
|
||||
minWidth={QUICK_FILTERS_MIN_WIDTH}
|
||||
maxWidth={QUICK_FILTERS_MAX_WIDTH}
|
||||
onResize={persistQuickFiltersWidth}
|
||||
resetToDefaultOnDoubleClick
|
||||
withHandle
|
||||
className={styles.quickFiltersContainer}
|
||||
handleTestId="quick-filters-resize-handle"
|
||||
>
|
||||
<div className={styles.categorySelectorSection}>
|
||||
<div className={styles.sectionHeader} data-type="resource">
|
||||
<Typography.Text className={styles.sectionLabel}>
|
||||
Viewing · Resource
|
||||
</Typography.Text>
|
||||
<div className={styles.sectionLine} />
|
||||
<Tooltip title="Collapse Filters">
|
||||
<ArrowUpToLine
|
||||
style={{ transform: 'rotate(270deg)' }}
|
||||
onClick={handleFilterVisibilityChange}
|
||||
size="md"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={styles.categoryCard}>
|
||||
<div className={styles.categoryList}>
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
key={category.key}
|
||||
type="button"
|
||||
className={`${styles.categoryItem} ${
|
||||
selectedCategory === category.key
|
||||
? styles.categoryItemSelected
|
||||
: ''
|
||||
}`}
|
||||
onClick={(): void => handleCategorySelect(category.key)}
|
||||
data-testid={`category-${category.key}`}
|
||||
>
|
||||
{category.icon}
|
||||
<Typography.Text>{category.label}</Typography.Text>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.quickFiltersContainer}>
|
||||
<div className={styles.quickFiltersContainerHeader}>
|
||||
<Typography.Text>Filters</Typography.Text>
|
||||
|
||||
<div className={styles.quickFiltersSection}>
|
||||
<div className={styles.sectionHeader} data-type="filter">
|
||||
<Typography.Text className={styles.sectionLabel}>
|
||||
Filter by
|
||||
</Typography.Text>
|
||||
<div className={styles.sectionLine} />
|
||||
</div>
|
||||
{selectedCategoryConfig && (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={selectedCategoryConfig}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
<Tooltip title="Collapse Filters">
|
||||
<ArrowUpToLine
|
||||
style={{ transform: 'rotate(270deg)' }}
|
||||
onClick={handleFilterVisibilityChange}
|
||||
size="md"
|
||||
/>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</ResizableBox>
|
||||
<Collapse
|
||||
onChange={handleCategoryChange}
|
||||
items={items}
|
||||
defaultActiveKey={[selectedCategory]}
|
||||
activeKey={[selectedCategory]}
|
||||
accordion
|
||||
bordered={false}
|
||||
ghost
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
|
||||
@@ -34,6 +34,8 @@ export const VIEW_TYPES = {
|
||||
METRICS: VIEWS.METRICS,
|
||||
LOGS: VIEWS.LOGS,
|
||||
TRACES: VIEWS.TRACES,
|
||||
CONTAINERS: VIEWS.CONTAINERS,
|
||||
PROCESSES: VIEWS.PROCESSES,
|
||||
EVENTS: VIEWS.EVENTS,
|
||||
};
|
||||
|
||||
@@ -392,6 +394,41 @@ export function GetClustersQuickFiltersConfig(
|
||||
];
|
||||
}
|
||||
|
||||
export function GetContainersQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const containerKey = dotMetricsEnabled
|
||||
? 'k8s.container.name'
|
||||
: 'k8s_container_name';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Container',
|
||||
attributeKey: {
|
||||
key: containerKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${containerKey}--string--resource`,
|
||||
},
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
defaultOpen: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function GetVolumesQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
.entityGroupHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-5);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Group } from '@signozhq/icons';
|
||||
|
||||
import styles from './EntityGroupHeader.module.scss';
|
||||
|
||||
interface EntityGroupHeaderProps {
|
||||
title: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
function EntityGroupHeader({
|
||||
title,
|
||||
icon,
|
||||
}: EntityGroupHeaderProps): JSX.Element {
|
||||
return (
|
||||
<div className={styles.entityGroupHeader}>
|
||||
{icon || <Group size={14} data-hide-expanded="true" />} {title}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EntityGroupHeader;
|
||||
@@ -1,666 +0,0 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Drawer, Tooltip } from 'antd';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { combineInitialAndUserExpression } from 'components/QueryBuilderV2/QueryV2/QuerySearch/utils';
|
||||
import { convertFiltersToExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import {
|
||||
initialQueryBuilderFormValuesMap,
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constants';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/types';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import {
|
||||
BarChart,
|
||||
ChevronsLeftRight,
|
||||
Compass,
|
||||
DraftingCompass,
|
||||
ScrollText,
|
||||
X,
|
||||
} from '@signozhq/icons';
|
||||
import { isCustomTimeRange, useGlobalTimeStore } from 'store/globalTime';
|
||||
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
TagFilter,
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
LogsAggregatorOperator,
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { InfraMonitoringEntity, VIEW_TYPES } from '../constants';
|
||||
import EntityEvents from '../EntityDetailsUtils/EntityEvents';
|
||||
import EntityLogs from '../EntityDetailsUtils/EntityLogs';
|
||||
import { K8S_ENTITY_LOGS_EXPRESSION_KEY } from '../EntityDetailsUtils/EntityLogs/hooks';
|
||||
import EntityMetrics from '../EntityDetailsUtils/EntityMetrics';
|
||||
import EntityTraces from '../EntityDetailsUtils/EntityTraces';
|
||||
import { K8S_ENTITY_TRACES_EXPRESSION_KEY } from '../EntityDetailsUtils/EntityTraces/hooks';
|
||||
import {
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringSelectedItem,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from '../hooks';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
|
||||
import '../EntityDetailsUtils/entityDetails.styles.scss';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
|
||||
const TimeRangeOffset = 1000000000;
|
||||
|
||||
export interface K8sDetailsMetadataConfig<T> {
|
||||
label: string;
|
||||
getValue: (entity: T) => string | number;
|
||||
render?: (value: string | number, entity: T) => React.ReactNode;
|
||||
}
|
||||
|
||||
export interface K8sDetailsFilters {
|
||||
filters: TagFilter;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
export interface K8sBaseDetailsProps<T> {
|
||||
category: InfraMonitoringEntity;
|
||||
eventCategory: string;
|
||||
// Data fetching configuration
|
||||
getSelectedItemFilters: (selectedItem: string) => TagFilter;
|
||||
fetchEntityData: (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
) => Promise<{ data: T | null; error?: string | null }>;
|
||||
// Entity configuration
|
||||
getEntityName: (entity: T) => string;
|
||||
getInitialLogTracesFilters: (entity: T) => TagFilterItem[];
|
||||
getInitialEventsFilters: (entity: T) => TagFilterItem[];
|
||||
metadataConfig: K8sDetailsMetadataConfig<T>[];
|
||||
entityWidgetInfo: {
|
||||
title: string;
|
||||
yAxisUnit: string;
|
||||
}[];
|
||||
getEntityQueryPayload: (
|
||||
entity: T,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
) => GetQueryResultsProps[];
|
||||
queryKeyPrefix: string;
|
||||
/** When true, only metrics are shown and the Metrics/Logs/Traces/Events tab bar is hidden. */
|
||||
hideDetailViewTabs?: boolean;
|
||||
tabsConfig?: {
|
||||
showMetrics?: boolean;
|
||||
showLogs?: boolean;
|
||||
showTraces?: boolean;
|
||||
showEvents?: boolean;
|
||||
};
|
||||
customTabs?: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
render: (props: {
|
||||
entity: T;
|
||||
timeRange: { startTime: number; endTime: number };
|
||||
selectedInterval: Time;
|
||||
handleTimeChange: (
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
}) => React.ReactNode;
|
||||
}>;
|
||||
}
|
||||
|
||||
export function createFilterItem(
|
||||
key: string,
|
||||
value: string,
|
||||
dataType: DataTypes = DataTypes.String,
|
||||
): TagFilterItem {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
key,
|
||||
dataType,
|
||||
type: 'resource',
|
||||
id: `${key}--string--resource--false`,
|
||||
},
|
||||
op: '=',
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export default function K8sBaseDetails<T>({
|
||||
category,
|
||||
eventCategory,
|
||||
getSelectedItemFilters,
|
||||
fetchEntityData,
|
||||
getEntityName,
|
||||
getInitialLogTracesFilters,
|
||||
getInitialEventsFilters,
|
||||
metadataConfig,
|
||||
entityWidgetInfo,
|
||||
getEntityQueryPayload,
|
||||
queryKeyPrefix,
|
||||
hideDetailViewTabs = false,
|
||||
tabsConfig,
|
||||
customTabs,
|
||||
}: K8sBaseDetailsProps<T>): JSX.Element {
|
||||
const selectedTime = useGlobalTimeStore((s) => s.selectedTime);
|
||||
const getMinMaxTime = useGlobalTimeStore((s) => s.getMinMaxTime);
|
||||
const lastComputedMinMax = useGlobalTimeStore((s) => s.lastComputedMinMax);
|
||||
const getAutoRefreshQueryKey = useGlobalTimeStore(
|
||||
(s) => s.getAutoRefreshQueryKey,
|
||||
);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const [selectedItem, setSelectedItem] = useInfraMonitoringSelectedItem();
|
||||
|
||||
const entityQueryKey = useMemo(
|
||||
() =>
|
||||
getAutoRefreshQueryKey(
|
||||
selectedTime,
|
||||
`${queryKeyPrefix}EntityDetails`,
|
||||
selectedItem,
|
||||
),
|
||||
[queryKeyPrefix, selectedItem, selectedTime, getAutoRefreshQueryKey],
|
||||
);
|
||||
|
||||
const {
|
||||
data: entityResponse,
|
||||
isLoading: isEntityLoading,
|
||||
isError: isEntityError,
|
||||
error: entityError,
|
||||
} = useQuery({
|
||||
queryKey: entityQueryKey,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!selectedItem) {
|
||||
return { data: null };
|
||||
}
|
||||
const filters = getSelectedItemFilters(selectedItem);
|
||||
const { minTime, maxTime } = getMinMaxTime();
|
||||
|
||||
return fetchEntityData(
|
||||
{
|
||||
filters,
|
||||
start: Math.floor(minTime / NANO_SECOND_MULTIPLIER),
|
||||
end: Math.floor(maxTime / NANO_SECOND_MULTIPLIER),
|
||||
},
|
||||
signal,
|
||||
);
|
||||
},
|
||||
enabled: !!selectedItem,
|
||||
});
|
||||
|
||||
const entity = entityResponse?.data ?? null;
|
||||
const hasResponseError = !!entityResponse?.error;
|
||||
|
||||
const logsAndTracesInitialExpression = useMemo(() => {
|
||||
if (!entity) {
|
||||
return '';
|
||||
}
|
||||
const primaryFiltersOnly = {
|
||||
op: 'AND' as const,
|
||||
items: getInitialLogTracesFilters(entity),
|
||||
};
|
||||
return convertFiltersToExpression(primaryFiltersOnly).expression;
|
||||
}, [entity, getInitialLogTracesFilters]);
|
||||
|
||||
const eventsInitialExpression = useMemo(() => {
|
||||
if (!entity) {
|
||||
return '';
|
||||
}
|
||||
const primaryFiltersOnly = {
|
||||
op: 'AND' as const,
|
||||
items: getInitialEventsFilters(entity),
|
||||
};
|
||||
return convertFiltersToExpression(primaryFiltersOnly).expression;
|
||||
}, [entity, getInitialEventsFilters]);
|
||||
|
||||
const handleClose = useCallback((): void => {
|
||||
setSelectedItem(null);
|
||||
}, [setSelectedItem]);
|
||||
|
||||
const entityName = entity ? getEntityName(entity) : '';
|
||||
|
||||
// Content state (previously in K8sBaseDetailsContent)
|
||||
const tabVisibility = useMemo(
|
||||
() => ({
|
||||
showMetrics: true,
|
||||
showLogs: true,
|
||||
showTraces: true,
|
||||
showEvents: true,
|
||||
...tabsConfig,
|
||||
}),
|
||||
[tabsConfig],
|
||||
);
|
||||
|
||||
const { startMs, endMs } = useMemo(
|
||||
() => ({
|
||||
startMs: Math.floor(lastComputedMinMax.minTime / NANO_SECOND_MULTIPLIER),
|
||||
endMs: Math.floor(lastComputedMinMax.maxTime / NANO_SECOND_MULTIPLIER),
|
||||
}),
|
||||
[lastComputedMinMax],
|
||||
);
|
||||
|
||||
const [modalTimeRange, setModalTimeRange] = useState(() => ({
|
||||
startTime: startMs,
|
||||
endTime: endMs,
|
||||
}));
|
||||
|
||||
// TODO(h4ad): Remove this and use context/zustand
|
||||
const lastSelectedInterval = useRef<Time | null>(null);
|
||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||
lastSelectedInterval.current
|
||||
? lastSelectedInterval.current
|
||||
: isCustomTimeRange(selectedTime)
|
||||
? DEFAULT_TIME_RANGE
|
||||
: selectedTime,
|
||||
);
|
||||
|
||||
const [selectedView, setSelectedView] = useInfraMonitoringView();
|
||||
const effectiveView = hideDetailViewTabs ? VIEW_TYPES.METRICS : selectedView;
|
||||
|
||||
const [, setLogFiltersParam] = useInfraMonitoringLogFilters();
|
||||
const [, setTracesFiltersParam] = useInfraMonitoringTracesFilters();
|
||||
const [, setEventsFiltersParam] = useInfraMonitoringEventsFilters();
|
||||
const [userLogsExpression] = useQueryState(
|
||||
K8S_ENTITY_LOGS_EXPRESSION_KEY,
|
||||
parseAsString,
|
||||
);
|
||||
const [userTracesExpression] = useQueryState(
|
||||
K8S_ENTITY_TRACES_EXPRESSION_KEY,
|
||||
parseAsString,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (entity) {
|
||||
logEvent(InfraMonitoringEvents.PageVisited, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
category: eventCategory,
|
||||
});
|
||||
}
|
||||
}, [entity, eventCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||
if (!isCustomTimeRange(currentSelectedInterval)) {
|
||||
setSelectedInterval(currentSelectedInterval);
|
||||
const { minTime, maxTime } = getMinMaxTime();
|
||||
|
||||
setModalTimeRange({
|
||||
startTime: Math.floor(minTime / TimeRangeOffset),
|
||||
endTime: Math.floor(maxTime / TimeRangeOffset),
|
||||
});
|
||||
}
|
||||
}, [getMinMaxTime, selectedTime]);
|
||||
|
||||
const handleTabChange = (value: string): void => {
|
||||
setSelectedView(value);
|
||||
setLogFiltersParam(null);
|
||||
setTracesFiltersParam(null);
|
||||
setEventsFiltersParam(null);
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
category: eventCategory,
|
||||
view: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTimeChange = useCallback(
|
||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||
lastSelectedInterval.current = interval as Time;
|
||||
setSelectedInterval(interval as Time);
|
||||
|
||||
if (interval === 'custom' && dateTimeRange) {
|
||||
setModalTimeRange({
|
||||
startTime: Math.floor(dateTimeRange[0] / 1000),
|
||||
endTime: Math.floor(dateTimeRange[1] / 1000),
|
||||
});
|
||||
} else {
|
||||
const { maxTime, minTime } = GetMinMax(interval);
|
||||
|
||||
setModalTimeRange({
|
||||
startTime: Math.floor(minTime / TimeRangeOffset),
|
||||
endTime: Math.floor(maxTime / TimeRangeOffset),
|
||||
});
|
||||
}
|
||||
|
||||
logEvent(InfraMonitoringEvents.TimeUpdated, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
category: eventCategory,
|
||||
interval,
|
||||
view: effectiveView,
|
||||
});
|
||||
},
|
||||
[eventCategory, effectiveView],
|
||||
);
|
||||
|
||||
const handleExplorePagesRedirect = (): void => {
|
||||
const urlQuery = new URLSearchParams();
|
||||
|
||||
if (selectedInterval !== 'custom') {
|
||||
urlQuery.set(QueryParams.relativeTime, selectedInterval);
|
||||
} else {
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
urlQuery.set(QueryParams.startTime, modalTimeRange.startTime.toString());
|
||||
urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString());
|
||||
}
|
||||
|
||||
logEvent(InfraMonitoringEvents.ExploreClicked, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
category: eventCategory,
|
||||
view: selectedView,
|
||||
});
|
||||
|
||||
if (selectedView === VIEW_TYPES.LOGS) {
|
||||
const fullExpression = combineInitialAndUserExpression(
|
||||
logsAndTracesInitialExpression,
|
||||
userLogsExpression || '',
|
||||
);
|
||||
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
queryType: 'builder',
|
||||
builder: {
|
||||
...initialQueryState.builder,
|
||||
queryData: [
|
||||
{
|
||||
...initialQueryBuilderFormValuesMap.logs,
|
||||
aggregateOperator: LogsAggregatorOperator.NOOP,
|
||||
expression: fullExpression,
|
||||
filter: { expression: fullExpression },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const fullExpression = combineInitialAndUserExpression(
|
||||
logsAndTracesInitialExpression,
|
||||
userTracesExpression || '',
|
||||
);
|
||||
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
queryType: 'builder',
|
||||
builder: {
|
||||
...initialQueryState.builder,
|
||||
queryData: [
|
||||
{
|
||||
...initialQueryBuilderFormValuesMap.traces,
|
||||
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||
expression: fullExpression,
|
||||
filter: { expression: fullExpression },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
width="70%"
|
||||
title={
|
||||
<>
|
||||
<Divider type="vertical" />
|
||||
<Typography.Text className="title">
|
||||
{entityName ||
|
||||
((isEntityError || hasResponseError) &&
|
||||
'Failed to load entity details') ||
|
||||
(isEntityLoading && 'Loading...') ||
|
||||
'-'}
|
||||
</Typography.Text>
|
||||
</>
|
||||
}
|
||||
placement="right"
|
||||
onClose={handleClose}
|
||||
open={!!selectedItem}
|
||||
style={{
|
||||
overscrollBehavior: 'contain',
|
||||
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
||||
}}
|
||||
className="entity-detail-drawer"
|
||||
destroyOnClose
|
||||
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
||||
>
|
||||
{isEntityLoading && <LoadingContainer />}
|
||||
{(isEntityError || hasResponseError) && (
|
||||
<div className="entity-error-container">
|
||||
<Typography.Text color="danger">
|
||||
{entityResponse?.error ||
|
||||
(entityError instanceof Error
|
||||
? entityError.message
|
||||
: 'Failed to load entity details')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
{entity && !isEntityLoading && !hasResponseError && (
|
||||
<>
|
||||
<div className="entity-detail-drawer__entity">
|
||||
<div className="entity-details-grid">
|
||||
<div className="labels-row">
|
||||
{metadataConfig.map((config) => (
|
||||
<Typography.Text
|
||||
key={config.label}
|
||||
color="muted"
|
||||
className="entity-details-metadata-label"
|
||||
>
|
||||
{config.label}
|
||||
</Typography.Text>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="values-row">
|
||||
{metadataConfig.map((config) => {
|
||||
const value = config.getValue(entity);
|
||||
const displayValue = String(value);
|
||||
return (
|
||||
<Typography.Text
|
||||
key={config.label}
|
||||
className="entity-details-metadata-value"
|
||||
>
|
||||
{config.render ? (
|
||||
config.render(value, entity)
|
||||
) : (
|
||||
<Tooltip title={displayValue}>{displayValue}</Tooltip>
|
||||
)}
|
||||
</Typography.Text>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!hideDetailViewTabs && (
|
||||
<div className="views-tabs-container">
|
||||
<ToggleGroupSimple
|
||||
type="single"
|
||||
className="views-tabs"
|
||||
onChange={handleTabChange}
|
||||
value={selectedView}
|
||||
items={[
|
||||
...(tabVisibility.showMetrics
|
||||
? [
|
||||
{
|
||||
value: VIEW_TYPES.METRICS,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
<BarChart size={14} />
|
||||
Metrics
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(tabVisibility.showLogs
|
||||
? [
|
||||
{
|
||||
value: VIEW_TYPES.LOGS,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
<ScrollText size={14} />
|
||||
Logs
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(tabVisibility.showTraces
|
||||
? [
|
||||
{
|
||||
value: VIEW_TYPES.TRACES,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
<DraftingCompass size={14} />
|
||||
Traces
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(tabVisibility.showEvents
|
||||
? [
|
||||
{
|
||||
value: VIEW_TYPES.EVENTS,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
<ChevronsLeftRight size={14} />
|
||||
Events
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(customTabs?.map((tab) => ({
|
||||
value: tab.key,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
{tab.icon}
|
||||
{tab.label}
|
||||
</div>
|
||||
),
|
||||
})) ?? []),
|
||||
]}
|
||||
/>
|
||||
|
||||
{selectedView === VIEW_TYPES.LOGS && (
|
||||
<Tooltip title="Go to Logs Explorer" placement="left">
|
||||
<Button
|
||||
icon={<Compass size={18} />}
|
||||
className="compass-button"
|
||||
onClick={handleExplorePagesRedirect}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.TRACES && (
|
||||
<Tooltip title="Go to Traces Explorer" placement="left">
|
||||
<Button
|
||||
icon={<Compass size={18} />}
|
||||
className="compass-button"
|
||||
onClick={handleExplorePagesRedirect}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{effectiveView === VIEW_TYPES.METRICS && (
|
||||
<EntityMetrics<T>
|
||||
entity={entity}
|
||||
selectedInterval={selectedInterval}
|
||||
timeRange={modalTimeRange}
|
||||
handleTimeChange={handleTimeChange}
|
||||
isModalTimeSelection
|
||||
entityWidgetInfo={entityWidgetInfo}
|
||||
getEntityQueryPayload={getEntityQueryPayload}
|
||||
category={category}
|
||||
queryKey={`${queryKeyPrefix}Metrics`}
|
||||
/>
|
||||
)}
|
||||
{effectiveView === VIEW_TYPES.LOGS && (
|
||||
<EntityLogs
|
||||
timeRange={modalTimeRange}
|
||||
isModalTimeSelection
|
||||
handleTimeChange={handleTimeChange}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey={`${queryKeyPrefix}Logs`}
|
||||
category={category}
|
||||
initialExpression={logsAndTracesInitialExpression}
|
||||
/>
|
||||
)}
|
||||
{effectiveView === VIEW_TYPES.TRACES && (
|
||||
<EntityTraces
|
||||
timeRange={modalTimeRange}
|
||||
isModalTimeSelection
|
||||
handleTimeChange={handleTimeChange}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey={`${queryKeyPrefix}Traces`}
|
||||
category={category}
|
||||
initialExpression={logsAndTracesInitialExpression}
|
||||
/>
|
||||
)}
|
||||
{effectiveView === VIEW_TYPES.EVENTS && tabVisibility.showEvents && (
|
||||
<EntityEvents
|
||||
timeRange={modalTimeRange}
|
||||
isModalTimeSelection
|
||||
handleTimeChange={handleTimeChange}
|
||||
selectedInterval={selectedInterval}
|
||||
category={category}
|
||||
queryKey={`${queryKeyPrefix}Events`}
|
||||
initialExpression={eventsInitialExpression}
|
||||
/>
|
||||
)}
|
||||
{customTabs?.map((tab) =>
|
||||
selectedView === tab.key ? (
|
||||
<React.Fragment key={tab.key}>
|
||||
{tab.render({
|
||||
entity,
|
||||
timeRange: modalTimeRange,
|
||||
selectedInterval,
|
||||
handleTimeChange,
|
||||
})}
|
||||
</React.Fragment>
|
||||
) : null,
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
.emptyStateContainer {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.k8SListTable {
|
||||
padding-left: var(--spacing-2);
|
||||
--tanstack-table-header-cell-bg: var(--l2-background);
|
||||
--tanstack-table-header-cell-color: var(--l2-foreground);
|
||||
--tanstack-table-cell-bg: var(--l2-background);
|
||||
--tanstack-table-cell-color: var(--l2-foreground);
|
||||
--tanstack-table-row-hover-bg: var(--l2-background-hover);
|
||||
--tanstack-table-row-active-bg: var(--l2-background-active);
|
||||
--tanstack-table-resize-handle-bg: var(--l2-background);
|
||||
--tanstack-table-resize-handle-hover-bg: var(--l2-border);
|
||||
--tanstack-table-row-height: 42px;
|
||||
|
||||
--tanstack-cell-padding-top-override: 5px;
|
||||
--tanstack-cell-padding-bottom-override: 5px;
|
||||
--tanstack-cell-padding-left-override: 5px;
|
||||
--tanstack-cell-padding-right-override: 5px;
|
||||
|
||||
--tanstack-expansion-first-col-padding-left: 30px;
|
||||
|
||||
&[data-has-group-by='false'] {
|
||||
--tanstack-cell-padding-left-first-column: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.paginationContainer {
|
||||
padding-bottom: var(--spacing-8);
|
||||
padding-right: var(--spacing-8);
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import TanStackTable, {
|
||||
TableColumnDef,
|
||||
useHiddenColumnIds,
|
||||
} from 'components/TanStackTableView';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { useGlobalTimeStore } from 'store/globalTime';
|
||||
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import {
|
||||
useInfraMonitoringFiltersK8s,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringPageListing,
|
||||
useInfraMonitoringPageSizeListing,
|
||||
} from '../hooks';
|
||||
import { K8sEmptyState } from './K8sEmptyState';
|
||||
import { K8sExpandedRow } from './K8sExpandedRow';
|
||||
import K8sHeader from './K8sHeader';
|
||||
import { K8sBaseFilters } from './types';
|
||||
import { getGroupedByMeta } from './utils';
|
||||
|
||||
import styles from './K8sBaseList.module.scss';
|
||||
import cx from 'classnames';
|
||||
|
||||
export type K8sBaseListEmptyStateContext = {
|
||||
isError: boolean;
|
||||
error?: string | null;
|
||||
totalCount: number;
|
||||
hasFilters: boolean;
|
||||
isLoading: boolean;
|
||||
rawData?: unknown;
|
||||
};
|
||||
|
||||
/** Base type constraint for K8s entity data */
|
||||
export type K8sEntityData = { meta?: Record<string, string> };
|
||||
|
||||
export type K8sBaseListProps<T extends K8sEntityData> = {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
entity: InfraMonitoringEntity;
|
||||
tableColumns: TableColumnDef<T>[];
|
||||
fetchListData: (
|
||||
filters: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
) => Promise<{
|
||||
data: T[];
|
||||
total: number;
|
||||
error?: string | null;
|
||||
rawData?: unknown;
|
||||
}>;
|
||||
/** Function to get the unique key for a row. */
|
||||
getRowKey?: (record: T) => string;
|
||||
/** Function to get the item key used for selection. Defaults to getRowKey if not provided. */
|
||||
getItemKey?: (record: T) => string;
|
||||
eventCategory: InfraMonitoringEvents;
|
||||
renderEmptyState?: (
|
||||
context: K8sBaseListEmptyStateContext,
|
||||
) => React.ReactNode | null;
|
||||
};
|
||||
|
||||
export function K8sBaseList<T extends K8sEntityData>({
|
||||
controlListPrefix,
|
||||
entity,
|
||||
tableColumns,
|
||||
fetchListData,
|
||||
getRowKey,
|
||||
getItemKey,
|
||||
eventCategory,
|
||||
renderEmptyState,
|
||||
}: K8sBaseListProps<T>): JSX.Element {
|
||||
const [queryFilters] = useInfraMonitoringFiltersK8s();
|
||||
const [currentPage] = useInfraMonitoringPageListing();
|
||||
const [currentPageSize] = useInfraMonitoringPageSizeListing();
|
||||
const [groupBy] = useInfraMonitoringGroupBy();
|
||||
const [orderBy] = useInfraMonitoringOrderBy();
|
||||
const [selectedItem, setSelectedItem] = useQueryState(
|
||||
'selectedItem',
|
||||
parseAsString,
|
||||
);
|
||||
|
||||
const columnStorageKey = `k8s-${entity}-columns`;
|
||||
const hiddenColumnIds = useHiddenColumnIds(columnStorageKey);
|
||||
|
||||
const selectedTime = useGlobalTimeStore((s) => s.selectedTime);
|
||||
const refreshInterval = useGlobalTimeStore((s) => s.refreshInterval);
|
||||
const isRefreshEnabled = useGlobalTimeStore((s) => s.isRefreshEnabled);
|
||||
const getMinMaxTime = useGlobalTimeStore((s) => s.getMinMaxTime);
|
||||
const getAutoRefreshQueryKey = useGlobalTimeStore(
|
||||
(s) => s.getAutoRefreshQueryKey,
|
||||
);
|
||||
|
||||
const queryKey = useMemo(() => {
|
||||
return getAutoRefreshQueryKey(
|
||||
selectedTime,
|
||||
'k8sBaseList',
|
||||
entity,
|
||||
String(currentPageSize),
|
||||
String(currentPage),
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(groupBy),
|
||||
);
|
||||
}, [
|
||||
getAutoRefreshQueryKey,
|
||||
selectedTime,
|
||||
entity,
|
||||
currentPageSize,
|
||||
currentPage,
|
||||
queryFilters,
|
||||
orderBy,
|
||||
groupBy,
|
||||
]);
|
||||
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey,
|
||||
queryFn: ({ signal }) => {
|
||||
const { minTime, maxTime } = getMinMaxTime();
|
||||
|
||||
return fetchListData(
|
||||
{
|
||||
limit: currentPageSize,
|
||||
offset: (currentPage - 1) * currentPageSize,
|
||||
filters: queryFilters || { items: [], op: 'AND' },
|
||||
start: Math.floor(minTime / NANO_SECOND_MULTIPLIER),
|
||||
end: Math.floor(maxTime / NANO_SECOND_MULTIPLIER),
|
||||
orderBy: orderBy || undefined,
|
||||
groupBy: groupBy?.length > 0 ? groupBy : undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
},
|
||||
refetchInterval: isRefreshEnabled ? refreshInterval : false,
|
||||
});
|
||||
|
||||
const pageData = data?.data ?? [];
|
||||
const totalCount = data?.total || 0;
|
||||
const hasFilters = (queryFilters?.items?.length ?? 0) > 0;
|
||||
|
||||
const getGroupKeyFn = useCallback(
|
||||
(item: T) => getGroupedByMeta(item, groupBy),
|
||||
[groupBy],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent(InfraMonitoringEvents.PageVisited, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
category: eventCategory,
|
||||
total: totalCount,
|
||||
});
|
||||
}, [eventCategory, totalCount]);
|
||||
|
||||
const handleRowClick = useCallback(
|
||||
(_record: T, itemKey: string): void => {
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedItem(itemKey);
|
||||
}
|
||||
|
||||
logEvent(InfraMonitoringEvents.ItemClicked, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
category: eventCategory,
|
||||
});
|
||||
},
|
||||
[eventCategory, groupBy.length, setSelectedItem],
|
||||
);
|
||||
|
||||
const handleRowClickNewTab = useCallback(
|
||||
(_record: T, itemKey: string): void => {
|
||||
if (groupBy.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build URL with selectedItem param
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('selectedItem', itemKey);
|
||||
openInNewTab(url.pathname + url.search);
|
||||
|
||||
logEvent(InfraMonitoringEvents.ItemClicked, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
category: eventCategory,
|
||||
});
|
||||
},
|
||||
[eventCategory, groupBy.length],
|
||||
);
|
||||
|
||||
const isGroupedByAttribute = groupBy.length > 0;
|
||||
|
||||
// Filter columns for expanded row based on parent's hidden columns
|
||||
const expandedRowColumns = useMemo(
|
||||
() => tableColumns.filter((col) => !hiddenColumnIds.includes(col.id)),
|
||||
[tableColumns, hiddenColumnIds],
|
||||
);
|
||||
|
||||
const renderExpandedRow = useCallback(
|
||||
(
|
||||
_record: T,
|
||||
rowKey: string,
|
||||
groupMeta?: Record<string, string>,
|
||||
): JSX.Element => (
|
||||
<K8sExpandedRow<T>
|
||||
rowKey={rowKey}
|
||||
groupMeta={groupMeta}
|
||||
entity={entity}
|
||||
tableColumns={expandedRowColumns}
|
||||
fetchListData={fetchListData}
|
||||
getRowKey={getRowKey}
|
||||
getItemKey={getItemKey}
|
||||
/>
|
||||
),
|
||||
[entity, fetchListData, getRowKey, getItemKey, expandedRowColumns],
|
||||
);
|
||||
|
||||
const getRowCanExpand = useCallback(
|
||||
(): boolean => isGroupedByAttribute,
|
||||
[isGroupedByAttribute],
|
||||
);
|
||||
|
||||
const showTableLoadingState = isLoading;
|
||||
|
||||
const emptyTableMessage: React.ReactNode = renderEmptyState?.({
|
||||
isError,
|
||||
error: data?.error,
|
||||
totalCount,
|
||||
hasFilters,
|
||||
isLoading: showTableLoadingState,
|
||||
rawData: data?.rawData,
|
||||
}) || (
|
||||
<K8sEmptyState
|
||||
isError={isError}
|
||||
error={data?.error}
|
||||
isLoading={showTableLoadingState}
|
||||
rawData={data?.rawData}
|
||||
/>
|
||||
);
|
||||
|
||||
const showEmptyState = !showTableLoadingState && pageData.length === 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sHeader
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={entity}
|
||||
showAutoRefresh={!selectedItem}
|
||||
columns={tableColumns}
|
||||
columnStorageKey={columnStorageKey}
|
||||
/>
|
||||
{isError && (
|
||||
<Typography>{data?.error?.toString() || 'Something went wrong'}</Typography>
|
||||
)}
|
||||
|
||||
{showEmptyState ? (
|
||||
<div className={styles.emptyStateContainer}>{emptyTableMessage}</div>
|
||||
) : (
|
||||
<TanStackTable<T>
|
||||
data={pageData}
|
||||
columns={tableColumns}
|
||||
columnStorageKey={columnStorageKey}
|
||||
isLoading={showTableLoadingState}
|
||||
getRowKey={getRowKey}
|
||||
getItemKey={getItemKey}
|
||||
groupBy={groupBy}
|
||||
getGroupKey={getGroupKeyFn}
|
||||
onRowClick={handleRowClick}
|
||||
onRowClickNewTab={handleRowClickNewTab}
|
||||
renderExpandedRow={isGroupedByAttribute ? renderExpandedRow : undefined}
|
||||
getRowCanExpand={isGroupedByAttribute ? getRowCanExpand : undefined}
|
||||
className={cx(styles.k8SListTable, expandedRowColumns)}
|
||||
enableQueryParams={{
|
||||
page: INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE,
|
||||
limit: INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE_SIZE,
|
||||
orderBy: INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY,
|
||||
expanded: INFRA_MONITORING_K8S_PARAMS_KEYS.EXPANDED,
|
||||
}}
|
||||
pagination={{
|
||||
total: totalCount,
|
||||
defaultLimit: 10,
|
||||
defaultPage: 1,
|
||||
showTotalCount: true,
|
||||
totalCountLabel: entity.charAt(0).toUpperCase() + entity.slice(1),
|
||||
}}
|
||||
paginationClassname={styles.paginationContainer}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user