Compare commits

..

121 Commits

Author SHA1 Message Date
Nikhil Mantri
56e79be6cd Merge branch 'infraM/v2_pods_list_api' into infraM/v2_onboarding_api 2026-04-27 15:05:47 +05:30
Nikhil Mantri
92d297ac9d Merge branch 'main' into infraM/v2_pods_list_api 2026-04-27 15:05:04 +05:30
Nikhil Mantri
b3c352609c Merge branch 'infraM/v2_pods_list_api' into infraM/v2_onboarding_api 2026-04-27 12:51:27 +05:30
Ashwin Bhatkal
bdbaa32485 Merge branch 'main' into infraM/v2_pods_list_api 2026-04-27 11:44:13 +05:30
nikhilmantri0902
9503cdff36 chore: added a note from otel 2026-04-25 21:42:33 +05:30
nikhilmantri0902
5a18786ab2 chore: added a note from otel 2026-04-25 21:29:41 +05:30
nikhilmantri0902
648154df14 chore: readability improvement 2026-04-25 19:30:30 +05:30
nikhilmantri0902
98eb002e07 chore: simplify 2026-04-24 18:14:48 +05:30
nikhilmantri0902
720379db9f chore: get onboarding spec 2026-04-24 17:47:03 +05:30
nikhilmantri0902
6ad14e7151 chore: renamed method 2026-04-24 17:38:56 +05:30
nikhilmantri0902
181fca064b chore: added onboarding api 2026-04-24 17:33:03 +05:30
nikhilmantri0902
a5e39ca6bd Merge branch 'infraM/v2_pods_list_api' into infraM/v2_onboarding_api 2026-04-24 17:25:27 +05:30
Ashwin Bhatkal
b35c6676f9 fix: rebase fixes 2026-04-24 13:17:02 +05:30
nikhilmantri0902
1095caa123 chore: improved api description to document -1 as no data in numeric fields 2026-04-24 12:11:45 +05:30
nikhilmantri0902
9043b49762 chore: removed pods - order by phase 2026-04-24 12:04:51 +05:30
nikhilmantri0902
d4084a7494 chore: added support for pod phase unknown 2026-04-24 11:46:26 +05:30
nikhilmantri0902
27c564b3bf chore: added required tags 2026-04-24 11:21:20 +05:30
Nikhil Mantri
f02c491828 Merge branch 'main' into infraM/v2_pods_list_api 2026-04-24 10:47:13 +05:30
Nikhil Mantri
3d53b8f77f Merge branch 'main' into infraM/v2_pods_list_api 2026-04-23 18:44:33 +05:30
nikhilmantri0902
dffe94fec4 chore: conflicts resolved 2026-04-23 18:39:39 +05:30
nikhilmantri0902
c9360fcf13 Merge branch 'infraM/v2_hosts_list_api' into infraM/v2_pods_list_api 2026-04-23 11:23:49 +05:30
nikhilmantri0902
b5ab45db20 chore: regen api client for inframonitoring
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:51:35 +05:30
Nikhil Mantri
08f76aca78 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-23 09:51:01 +05:30
nikhilmantri0902
d81cec4c29 chore: added onboarding splits 2026-04-22 19:22:37 +05:30
nikhilmantri0902
49744c6104 chore: added attrs presence check function 2026-04-22 19:06:26 +05:30
nikhilmantri0902
2147627baf chore: added specs for all component types 2026-04-22 19:00:46 +05:30
nikhilmantri0902
824f92a88f chore: added types and constants 2026-04-22 18:21:55 +05:30
nikhilmantri0902
983d4fe4f2 Merge branch 'infraM/v2_hosts_list_api' into infraM/v2_pods_list_api 2026-04-22 15:37:21 +05:30
nikhilmantri0902
833af794c3 chore: make sort stable in case of tiebreaker by comparing composite group by keys 2026-04-22 15:26:28 +05:30
nikhilmantri0902
21b51d1fcc chore: cleanup and rename 2026-04-22 15:13:00 +05:30
nikhilmantri0902
56f22682c8 Merge branch 'infraM/v2_hosts_list_api' into infraM/v2_pods_list_api 2026-04-22 14:29:17 +05:30
nikhilmantri0902
9c8359940c chore: remove a defensive nil map check, the function ensure non-nil map when err nil 2026-04-22 11:59:01 +05:30
Nikhil Mantri
4050880275 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-22 11:35:57 +05:30
nikhilmantri0902
5e775f64f2 chore: added status unauthorized 2026-04-21 21:30:44 +05:30
nikhilmantri0902
0189f23f46 chore: removed internal server error 2026-04-21 21:30:01 +05:30
nikhilmantri0902
49a36d4e3d chore: removed pod metric temporalities 2026-04-21 21:24:49 +05:30
nikhilmantri0902
9407d658ab chore: merge base hosts v2 branch 2026-04-21 21:17:28 +05:30
nikhilmantri0902
5035712485 chore: added json tag required: true 2026-04-21 18:50:25 +05:30
nikhilmantri0902
bab17c3615 chore: comments resolve 2026-04-21 18:33:56 +05:30
Nikhil Mantri
37b44f4db9 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-21 17:40:06 +05:30
nikhilmantri0902
99dd6e5f1e chore: pods code restructuring 2026-04-21 17:03:13 +05:30
nikhilmantri0902
9c7131fa6a chore: merge base branch 2026-04-21 16:22:55 +05:30
Nikhil Mantri
ad889a2e1d Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-21 13:48:53 +05:30
nikhilmantri0902
a4f6d0cbf5 chore: removed temporalities 2026-04-21 13:44:06 +05:30
nikhilmantri0902
589bed7c16 chore: comments correction 2026-04-21 12:50:51 +05:30
nikhilmantri0902
93843a1f48 chore: file structure further breakdown for clarity 2026-04-21 12:36:07 +05:30
nikhilmantri0902
88c43108fc chore: added types package 2026-04-20 18:52:43 +05:30
nikhilmantri0902
ed4cf540e8 chore: inframonitoring types renaming 2026-04-20 18:47:28 +05:30
nikhilmantri0902
9e2dfa9033 chore: rearrangement 2026-04-20 17:51:03 +05:30
nikhilmantri0902
d98d5d68ee chore: rename PodsList -> ListPods 2026-04-20 16:57:21 +05:30
nikhilmantri0902
2cb1c3b73b chore: rename HostsList -> ListHosts 2026-04-20 16:42:19 +05:30
nikhilmantri0902
ae7ca497ad chore: merged base hosts branch and reorganized code 2026-04-20 13:38:25 +05:30
Nikhil Mantri
a579916961 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-20 11:05:36 +05:30
Nikhil Mantri
4a16d56abf feat(infra-monitoring): v2 hosts list - return counts of active & inactive hosts for custom group by attributes (#10956)
* chore: add functionality for showing active and inactive counts in custom group by

* chore: bug fix

* chore: added subquery for active and total count

* chore: ignore empty string hosts in get active hosts

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

* chore: refactor code
2026-04-20 10:41:15 +05:30
Nikhil Mantri
642b5ac3f0 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-16 16:32:39 +05:30
Nikhil Mantri
a12112619c Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-16 15:41:35 +05:30
nikhilmantri0902
014785f1bc chore: ignore empty string hosts in get active hosts 2026-04-16 13:17:15 +05:30
Nikhil Mantri
58ee797b10 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-15 14:18:29 +05:30
Nikhil Mantri
82d236742f Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-15 11:21:33 +05:30
nikhilmantri0902
397e1ad5be chore: added TODOs and made filterByStatus a part of filter struct 2026-04-14 18:32:48 +05:30
nikhilmantri0902
8d6b25ca9b chore: resolved conflicts 2026-04-14 17:09:17 +05:30
nikhilmantri0902
5fa6bd8b8d Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-13 11:02:14 +05:30
nikhilmantri0902
bd9977483b chore: improved description 2026-04-11 11:31:35 +05:30
nikhilmantri0902
50fbdfeeef chore: validate order by to validate function 2026-04-10 19:01:45 +05:30
nikhilmantri0902
e2b1b73e87 chore: improvements 2026-04-10 13:23:33 +05:30
nikhilmantri0902
cb9f3fd3e5 chore: rearrage 2026-04-10 00:39:23 +05:30
nikhilmantri0902
232acc343d chore: escape backtick to prevent sql injection 2026-04-10 00:01:01 +05:30
nikhilmantri0902
2025afdccc chore: endpoint modification openapi 2026-04-09 23:25:59 +05:30
nikhilmantri0902
d2f4d4af93 chore: endpoint correction 2026-04-09 23:21:57 +05:30
Nikhil Mantri
47ff7bbb8e Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-09 23:20:39 +05:30
Nikhil Mantri
724071c5dc Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-09 18:30:15 +05:30
nikhilmantri0902
4d24979358 chore: frontend fix 2026-04-09 18:26:42 +05:30
nikhilmantri0902
042943b10a chore: distributed samples table to local table change for get metadata 2026-04-09 18:24:45 +05:30
nikhilmantri0902
48a9be7ec8 chore: added required metrics check 2026-04-09 17:38:48 +05:30
nikhilmantri0902
a9504b2120 chore: added a TODO remark 2026-04-09 16:08:34 +05:30
nikhilmantri0902
8755887c4a chore: added better metrics existence check 2026-04-09 16:01:35 +05:30
Nikhil Mantri
4cb4662b3a Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-09 15:14:25 +05:30
nikhilmantri0902
e6900dabc8 chore: warnings added passing from queryResponse warning to host lists response struct 2026-04-09 00:09:38 +05:30
nikhilmantri0902
c1ba389b63 chore: add type for response and files rearrange 2026-04-08 23:35:53 +05:30
nikhilmantri0902
3a1f40234f Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-08 23:03:50 +05:30
Nikhil Mantri
2e4891fa63 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-08 16:07:57 +05:30
Nikhil Mantri
04ebc0bec7 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-08 11:08:10 +05:30
nikhilmantri0902
271f9b81ed Merge branch 'infraM/v2_hosts_list_api' into infraM/v2_pods_list_api 2026-04-07 21:55:47 +05:30
nikhilmantri0902
6fa815c294 chore: modified getMetadata query 2026-04-07 18:55:57 +05:30
nikhilmantri0902
63ec518efb chore: added hostName logic 2026-04-07 17:36:15 +05:30
nikhilmantri0902
c4ca20dd90 chore: return errors from getMetadata and lint fix 2026-04-07 17:01:13 +05:30
nikhilmantri0902
e56cc4222b chore: return errors from getMetadata and lint fix 2026-04-07 16:57:35 +05:30
nikhilmantri0902
07d2944d7c chore: yarn generate api 2026-04-07 16:44:06 +05:30
nikhilmantri0902
dea01ae36a chore: hostStatusNone added for clarity that this field can be left empty as well in payload 2026-04-07 16:32:25 +05:30
nikhilmantri0902
62ea5b54e2 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-07 14:09:48 +05:30
nikhilmantri0902
e549a7e42f chore: added pods list api updates 2026-04-07 13:58:10 +05:30
nikhilmantri0902
90e2ebb11f Merge branch 'infraM/v2_hosts_list_api' into infraM/v2_pods_list_api 2026-04-07 13:51:35 +05:30
nikhilmantri0902
61baa1be7a chore: code improvements 2026-04-07 13:49:00 +05:30
nikhilmantri0902
b946fa665f Merge branch 'infraM/v2_hosts_list_api' into infraM/v2_pods_list_api 2026-04-07 11:15:35 +05:30
nikhilmantri0902
2e049556e4 chore: unified composite key function 2026-04-07 11:15:03 +05:30
nikhilmantri0902
492a5e70d7 chore: added pods metrics temporality 2026-04-06 17:33:44 +05:30
nikhilmantri0902
ba1f2771e8 Merge branch 'infraM/v2_hosts_list_api' into infraM/v2_pods_list_api 2026-04-06 17:18:44 +05:30
nikhilmantri0902
7458fb4855 Merge branch 'main' into infraM/v2_hosts_list_api 2026-04-06 17:18:01 +05:30
nikhilmantri0902
5f55f3938b chore: added temporalities of metrics 2026-04-06 17:17:15 +05:30
nikhilmantri0902
3e8102485c Merge branch 'infraM/v2_hosts_list_api' into infraM/v2_pods_list_api 2026-04-04 20:52:50 +05:30
nikhilmantri0902
861c682ea5 chore: nil pointer dereference fix in req.Filter 2026-04-04 20:52:08 +05:30
nikhilmantri0902
c8e5895dff chore: nil pointer check 2026-04-04 20:45:04 +05:30
nikhilmantri0902
82d72e7edb chore: pods api meta start time 2026-04-04 17:18:04 +05:30
nikhilmantri0902
a3f8ecaaf1 chore: merged base branch 2026-04-04 16:47:10 +05:30
nikhilmantri0902
19aada656c chore: updated spec 2026-04-04 16:44:15 +05:30
nikhilmantri0902
b21bb4280f chore: updated openapi yml 2026-04-04 16:38:22 +05:30
nikhilmantri0902
bc0a4fdb5c chore: added pods list logic 2026-04-04 13:24:46 +05:30
nikhilmantri0902
37fb0e9254 Merge branch 'infraM/base_dependencies' into infraM/v2_hosts_list_api 2026-04-03 17:49:00 +05:30
nikhilmantri0902
aecfa1a174 chore: added validation on order by 2026-04-02 20:13:30 +05:30
nikhilmantri0902
b869d23d94 chore: moved funcs 2026-04-02 20:02:22 +05:30
nikhilmantri0902
6ee3d44f76 chore: removed isSendingK8sAgentsMetricsCode 2026-04-02 19:58:30 +05:30
nikhilmantri0902
462e554107 chore: yarn generate api 2026-04-02 14:49:15 +05:30
nikhilmantri0902
66afa73e6f chore: return status as a string 2026-04-02 14:39:02 +05:30
nikhilmantri0902
54c604bcf4 chore: added some unit tests 2026-04-02 14:20:27 +05:30
nikhilmantri0902
c1be02ba54 chore: added validate function 2026-04-02 14:14:34 +05:30
nikhilmantri0902
d3c7ba8f45 chore: disk usage 2026-04-02 14:01:18 +05:30
nikhilmantri0902
039c4a0496 fix: bug fix 2026-04-02 11:32:49 +05:30
nikhilmantri0902
51a94b6bbc chore: added logic for hosts v3 api 2026-04-02 02:52:28 +05:30
nikhilmantri0902
bbfbb94f52 chore: merged main 2026-04-01 00:45:40 +05:30
nikhilmantri0902
d1eb9ef16f chore: endpoint detail update 2026-03-31 16:16:31 +05:30
nikhilmantri0902
3db00f8bc3 chore: baseline setup 2026-03-31 15:27:18 +05:30
66 changed files with 2570 additions and 3241 deletions

View File

@@ -167,7 +167,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
if err != nil {
return nil, err
}
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider(defStore)
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider()
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,

View File

@@ -2474,6 +2474,82 @@ components:
- requiredMetricsCheck
- endTimeBeforeRetention
type: object
InframonitoringtypesPodPhase:
enum:
- pending
- running
- succeeded
- failed
- unknown
- ""
type: string
InframonitoringtypesPodRecord:
properties:
meta:
additionalProperties: {}
nullable: true
type: object
podAge:
format: int64
type: integer
podCPU:
format: double
type: number
podCPULimit:
format: double
type: number
podCPURequest:
format: double
type: number
podMemory:
format: double
type: number
podMemoryLimit:
format: double
type: number
podMemoryRequest:
format: double
type: number
podPhase:
$ref: '#/components/schemas/InframonitoringtypesPodPhase'
podUID:
type: string
required:
- podUID
- podCPU
- podCPURequest
- podCPULimit
- podMemory
- podMemoryRequest
- podMemoryLimit
- podPhase
- podAge
- meta
type: object
InframonitoringtypesPods:
properties:
endTimeBeforeRetention:
type: boolean
records:
items:
$ref: '#/components/schemas/InframonitoringtypesPodRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
total:
type: integer
type:
$ref: '#/components/schemas/InframonitoringtypesResponseType'
warning:
$ref: '#/components/schemas/Querybuildertypesv5QueryWarnData'
required:
- type
- records
- total
- requiredMetricsCheck
- endTimeBeforeRetention
type: object
InframonitoringtypesPostableHosts:
properties:
end:
@@ -2500,6 +2576,32 @@ components:
- end
- limit
type: object
InframonitoringtypesPostablePods:
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
InframonitoringtypesRequiredMetricsCheck:
properties:
missingMetrics:
@@ -2933,8 +3035,8 @@ components:
type: object
PromotetypesWrappedIndex:
properties:
fieldDataType:
$ref: '#/components/schemas/TelemetrytypesFieldDataType'
column_type:
type: string
granularity:
type: integer
type:
@@ -4496,184 +4598,6 @@ components:
type: object
Sigv4SigV4Config:
type: object
SpantypesFieldContext:
enum:
- attribute
- resource
type: string
SpantypesGettableSpanMapperGroups:
properties:
items:
items:
$ref: '#/components/schemas/SpantypesSpanMapperGroup'
type: array
required:
- items
type: object
SpantypesPostableSpanMapper:
properties:
config:
$ref: '#/components/schemas/SpantypesSpanMapperConfig'
enabled:
type: boolean
field_context:
$ref: '#/components/schemas/SpantypesFieldContext'
name:
type: string
required:
- name
- field_context
- config
type: object
SpantypesPostableSpanMapperGroup:
properties:
category:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
enabled:
type: boolean
name:
type: string
required:
- name
- category
- condition
type: object
SpantypesSpanMapper:
properties:
config:
$ref: '#/components/schemas/SpantypesSpanMapperConfig'
createdAt:
format: date-time
type: string
createdBy:
type: string
enabled:
type: boolean
field_context:
$ref: '#/components/schemas/SpantypesFieldContext'
group_id:
type: string
id:
type: string
name:
type: string
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- group_id
- name
- field_context
- config
- enabled
type: object
SpantypesSpanMapperConfig:
properties:
sources:
items:
$ref: '#/components/schemas/SpantypesSpanMapperSource'
nullable: true
type: array
required:
- sources
type: object
SpantypesSpanMapperGroup:
properties:
category:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
createdAt:
format: date-time
type: string
createdBy:
type: string
enabled:
type: boolean
id:
type: string
name:
type: string
orgId:
type: string
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- orgId
- name
- category
- condition
- enabled
type: object
SpantypesSpanMapperGroupCategory:
type: object
SpantypesSpanMapperGroupCondition:
properties:
attributes:
items:
type: string
nullable: true
type: array
resource:
items:
type: string
nullable: true
type: array
required:
- attributes
- resource
type: object
SpantypesSpanMapperOperation:
enum:
- move
- copy
type: string
SpantypesSpanMapperSource:
properties:
context:
$ref: '#/components/schemas/SpantypesFieldContext'
key:
type: string
operation:
$ref: '#/components/schemas/SpantypesSpanMapperOperation'
priority:
type: integer
required:
- key
- context
- operation
- priority
type: object
SpantypesUpdatableSpanMapper:
properties:
config:
$ref: '#/components/schemas/SpantypesSpanMapperConfig'
enabled:
nullable: true
type: boolean
field_context:
$ref: '#/components/schemas/SpantypesFieldContext'
type: object
SpantypesUpdatableSpanMapperGroup:
properties:
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
enabled:
nullable: true
type: boolean
name:
nullable: true
type: string
type: object
TelemetrytypesFieldContext:
enum:
- metric
@@ -9416,487 +9340,6 @@ paths:
summary: Updates my service account
tags:
- serviceaccount
/api/v1/span_mapper_groups:
get:
deprecated: false
description: Returns all span attribute mapping groups for the authenticated
org.
operationId: ListSpanMapperGroups
parameters:
- explode: true
in: query
name: category
schema:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
style: deepObject
- in: query
name: enabled
schema:
nullable: true
type: boolean
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesGettableSpanMapperGroups'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List span attribute mapping groups
tags:
- spanmapper
post:
deprecated: false
description: Creates a new span attribute mapping group for the org.
operationId: CreateSpanMapperGroup
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesPostableSpanMapperGroup'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesSpanMapperGroup'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Conflict
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create a span attribute mapping group
tags:
- spanmapper
/api/v1/span_mapper_groups/{groupId}:
delete:
deprecated: false
description: Hard-deletes a mapping group and cascades to all its mappers.
operationId: DeleteSpanMapperGroup
parameters:
- in: path
name: groupId
required: true
schema:
type: string
responses:
"204":
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete a span attribute mapping group
tags:
- spanmapper
patch:
deprecated: false
description: Partially updates an existing mapping group's name, condition,
or enabled state.
operationId: UpdateSpanMapperGroup
parameters:
- in: path
name: groupId
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesUpdatableSpanMapperGroup'
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Update a span attribute mapping group
tags:
- spanmapper
/api/v1/span_mapper_groups/{groupId}/span_mappers:
get:
deprecated: false
description: Returns all mappers belonging to a mapping group.
operationId: ListSpanMappers
parameters:
- in: path
name: groupId
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesGettableSpanMapperGroups'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List span mappers for a group
tags:
- spanmapper
post:
deprecated: false
description: Adds a new mapper to the specified mapping group.
operationId: CreateSpanMapper
parameters:
- in: path
name: groupId
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesPostableSpanMapper'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesSpanMapper'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Conflict
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create a span mapper
tags:
- spanmapper
/api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}:
delete:
deprecated: false
description: Hard-deletes a mapper from a mapping group.
operationId: DeleteSpanMapper
parameters:
- in: path
name: groupId
required: true
schema:
type: string
- in: path
name: mapperId
required: true
schema:
type: string
responses:
"204":
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete a span mapper
tags:
- spanmapper
patch:
deprecated: false
description: Partially updates an existing mapper's field context, config, or
enabled state.
operationId: UpdateSpanMapper
parameters:
- in: path
name: groupId
required: true
schema:
type: string
- in: path
name: mapperId
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesUpdatableSpanMapper'
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Update a span mapper
tags:
- spanmapper
/api/v1/testChannel:
post:
deprecated: true
@@ -10886,7 +10329,9 @@ paths:
five metrics, and pagination via offset/limit. The response type is ''list''
for the default host.name grouping or ''grouped_list'' for custom groupBy
keys. Also reports missing required metrics and whether the requested time
range falls before the data retention boundary.'
range falls before the data retention boundary. Numeric metric fields (cpu,
memory, wait, load15, diskUsage) return -1 as a sentinel when no data is available
for that field; frontends should render ''—'' rather than the literal value.'
operationId: ListHosts
requestBody:
content:
@@ -10940,6 +10385,76 @@ paths:
summary: List Hosts for Infra Monitoring
tags:
- inframonitoring
/api/v2/infra_monitoring/pods:
post:
deprecated: false
description: 'Returns a paginated list of Kubernetes pods with key metrics:
CPU usage, CPU request/limit utilization, memory working set, memory request/limit
utilization, current pod phase (pending/running/succeeded/failed/unknown),
and pod age (ms since start time). Each pod includes metadata attributes (namespace,
node, workload owner such as deployment/statefulset/daemonset/job/cronjob,
cluster). Supports filtering via a filter expression, custom groupBy to aggregate
pods by any attribute, ordering by any of the six metrics (cpu, cpu_request,
cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit.
The response type is ''list'' for the default k8s.pod.uid grouping or ''grouped_list''
for custom groupBy keys. Also reports missing required metrics and whether
the requested time range falls before the data retention boundary. Numeric
metric fields (podCPU, podCPURequest, podCPULimit, podMemory, podMemoryRequest,
podMemoryLimit, podAge) return -1 as a sentinel when no data is available
for that field; frontends should render ''—'' rather than the literal value.'
operationId: ListPods
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/InframonitoringtypesPostablePods'
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/InframonitoringtypesPods'
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 Pods for Infra Monitoring
tags:
- inframonitoring
/api/v2/livez:
get:
deprecated: false

View File

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

View File

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

View File

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

View File

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

View File

@@ -3243,6 +3243,88 @@ export interface InframonitoringtypesHostsDTO {
warning?: Querybuildertypesv5QueryWarnDataDTO;
}
export enum InframonitoringtypesPodPhaseDTO {
pending = 'pending',
running = 'running',
succeeded = 'succeeded',
failed = 'failed',
unknown = 'unknown',
'' = '',
}
/**
* @nullable
*/
export type InframonitoringtypesPodRecordDTOMeta = {
[key: string]: unknown;
} | null;
export interface InframonitoringtypesPodRecordDTO {
/**
* @type object
* @nullable true
*/
meta: InframonitoringtypesPodRecordDTOMeta;
/**
* @type integer
* @format int64
*/
podAge: number;
/**
* @type number
* @format double
*/
podCPU: number;
/**
* @type number
* @format double
*/
podCPULimit: number;
/**
* @type number
* @format double
*/
podCPURequest: number;
/**
* @type number
* @format double
*/
podMemory: number;
/**
* @type number
* @format double
*/
podMemoryLimit: number;
/**
* @type number
* @format double
*/
podMemoryRequest: number;
podPhase: InframonitoringtypesPodPhaseDTO;
/**
* @type string
*/
podUID: string;
}
export interface InframonitoringtypesPodsDTO {
/**
* @type boolean
*/
endTimeBeforeRetention: boolean;
/**
* @type array
* @nullable true
*/
records: InframonitoringtypesPodRecordDTO[] | null;
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
*/
total: number;
type: InframonitoringtypesResponseTypeDTO;
warning?: Querybuildertypesv5QueryWarnDataDTO;
}
export interface InframonitoringtypesPostableHostsDTO {
/**
* @type integer
@@ -3271,6 +3353,34 @@ export interface InframonitoringtypesPostableHostsDTO {
start: number;
}
export interface InframonitoringtypesPostablePodsDTO {
/**
* @type integer
* @format int64
*/
end: number;
filter?: Querybuildertypesv5FilterDTO;
/**
* @type array
* @nullable true
*/
groupBy?: Querybuildertypesv5GroupByKeyDTO[] | null;
/**
* @type integer
*/
limit: number;
/**
* @type integer
*/
offset?: number;
orderBy?: Querybuildertypesv5OrderByDTO;
/**
* @type integer
* @format int64
*/
start: number;
}
export interface InframonitoringtypesRequiredMetricsCheckDTO {
/**
* @type array
@@ -3701,7 +3811,10 @@ export interface PromotetypesPromotePathDTO {
}
export interface PromotetypesWrappedIndexDTO {
fieldDataType?: TelemetrytypesFieldDataTypeDTO;
/**
* @type string
*/
column_type?: string;
/**
* @type integer
*/
@@ -5472,187 +5585,6 @@ export interface Sigv4SigV4ConfigDTO {
[key: string]: unknown;
}
export enum SpantypesFieldContextDTO {
attribute = 'attribute',
resource = 'resource',
}
export interface SpantypesGettableSpanMapperGroupsDTO {
/**
* @type array
*/
items: SpantypesSpanMapperGroupDTO[];
}
export interface SpantypesPostableSpanMapperDTO {
config: SpantypesSpanMapperConfigDTO;
/**
* @type boolean
*/
enabled?: boolean;
field_context: SpantypesFieldContextDTO;
/**
* @type string
*/
name: string;
}
export interface SpantypesPostableSpanMapperGroupDTO {
category: SpantypesSpanMapperGroupCategoryDTO;
condition: SpantypesSpanMapperGroupConditionDTO;
/**
* @type boolean
*/
enabled?: boolean;
/**
* @type string
*/
name: string;
}
export interface SpantypesSpanMapperDTO {
config: SpantypesSpanMapperConfigDTO;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
field_context: SpantypesFieldContextDTO;
/**
* @type string
*/
group_id: string;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export interface SpantypesSpanMapperConfigDTO {
/**
* @type array
* @nullable true
*/
sources: SpantypesSpanMapperSourceDTO[] | null;
}
export interface SpantypesSpanMapperGroupDTO {
category: SpantypesSpanMapperGroupCategoryDTO;
condition: SpantypesSpanMapperGroupConditionDTO;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name: string;
/**
* @type string
*/
orgId: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export interface SpantypesSpanMapperGroupCategoryDTO {
[key: string]: unknown;
}
export interface SpantypesSpanMapperGroupConditionDTO {
/**
* @type array
* @nullable true
*/
attributes: string[] | null;
/**
* @type array
* @nullable true
*/
resource: string[] | null;
}
export enum SpantypesSpanMapperOperationDTO {
move = 'move',
copy = 'copy',
}
export interface SpantypesSpanMapperSourceDTO {
context: SpantypesFieldContextDTO;
/**
* @type string
*/
key: string;
operation: SpantypesSpanMapperOperationDTO;
/**
* @type integer
*/
priority: number;
}
export interface SpantypesUpdatableSpanMapperDTO {
config?: SpantypesSpanMapperConfigDTO;
/**
* @type boolean
* @nullable true
*/
enabled?: boolean | null;
field_context?: SpantypesFieldContextDTO;
}
export interface SpantypesUpdatableSpanMapperGroupDTO {
condition?: SpantypesSpanMapperGroupConditionDTO;
/**
* @type boolean
* @nullable true
*/
enabled?: boolean | null;
/**
* @type string
* @nullable true
*/
name?: string | null;
}
export enum TelemetrytypesFieldContextDTO {
metric = 'metric',
log = 'log',
@@ -7103,71 +7035,6 @@ export type GetMyServiceAccount200 = {
status: string;
};
export type ListSpanMapperGroupsParams = {
/**
* @description undefined
*/
category?: SpantypesSpanMapperGroupCategoryDTO;
/**
* @type boolean
* @nullable true
* @description undefined
*/
enabled?: boolean | null;
};
export type ListSpanMapperGroups200 = {
data: SpantypesGettableSpanMapperGroupsDTO;
/**
* @type string
*/
status: string;
};
export type CreateSpanMapperGroup201 = {
data: SpantypesSpanMapperGroupDTO;
/**
* @type string
*/
status: string;
};
export type DeleteSpanMapperGroupPathParameters = {
groupId: string;
};
export type UpdateSpanMapperGroupPathParameters = {
groupId: string;
};
export type ListSpanMappersPathParameters = {
groupId: string;
};
export type ListSpanMappers200 = {
data: SpantypesGettableSpanMapperGroupsDTO;
/**
* @type string
*/
status: string;
};
export type CreateSpanMapperPathParameters = {
groupId: string;
};
export type CreateSpanMapper201 = {
data: SpantypesSpanMapperDTO;
/**
* @type string
*/
status: string;
};
export type DeleteSpanMapperPathParameters = {
groupId: string;
mapperId: string;
};
export type UpdateSpanMapperPathParameters = {
groupId: string;
mapperId: string;
};
export type ListUsersDeprecated200 = {
/**
* @type array
@@ -7350,6 +7217,14 @@ export type ListHosts200 = {
status: string;
};
export type ListPods200 = {
data: InframonitoringtypesPodsDTO;
/**
* @type string
*/
status: string;
};
export type Livez200 = {
data: FactoryResponseDTO;
/**

View File

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

View File

@@ -34,7 +34,6 @@ import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import { getBaseUrl } from 'utils/basePath';
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
import CancelSubscriptionBanner from './CancelSubscriptionBanner';
import { BillingUsageGraph } from './BillingUsageGraph/BillingUsageGraph';
import { prepareCsvData } from './BillingUsageGraph/utils';
@@ -536,8 +535,6 @@ export default function BillingContainer(): JSX.Element {
{(isLoading || isFetchingBillingData) && renderTableSkeleton()}
</div>
{trialInfo?.trialConvertedToSubscription && <CancelSubscriptionBanner />}
{!trialInfo?.trialConvertedToSubscription && (
<div className="upgrade-plan-benefits">
<Row

View File

@@ -1,35 +0,0 @@
.cancel-subscription-banner {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--padding-4);
border-radius: 4px;
border: 1px solid var(--callout-error-border);
background-color: var(--callout-error-background);
margin: var(--spacing-4) 0 var(--spacing-12);
&__info {
display: flex;
flex-direction: column;
gap: var(--spacing-2);
}
&__title {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--callout-error-title);
}
&__subtitle {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
line-height: var(--paragraph-base-400-line-height);
color: var(--callout-error-icon);
}
}
.cancel-subscription-dialog__body {
font-size: var(--font-size-sm);
line-height: var(--line-height-relaxed);
color: var(--l2-foreground);
}

View File

@@ -1,96 +0,0 @@
import { useState } from 'react';
import { X } from '@signozhq/icons';
import { Button, DialogWrapper } from '@signozhq/ui';
import { useAppContext } from 'providers/App/App';
import { getBaseUrl } from 'utils/basePath';
import './CancelSubscriptionBanner.styles.scss';
function CancelSubscriptionBanner(): JSX.Element {
const [open, setOpen] = useState(false);
const { user, org } = useAppContext();
const handleContactSupport = (): void => {
const subject = encodeURIComponent('Cancel My SigNoz Subscription');
const orgName = org?.[0]?.displayName ?? '';
const body = encodeURIComponent(
[
'Hi SigNoz Team,',
'',
'I would like to cancel my SigNoz Cloud subscription.',
'Please find my account details below.',
'',
'Account Details:',
` • SigNoz URL: ${getBaseUrl()}`,
...(orgName ? [` • Organization: ${orgName}`] : []),
` • Account Email: ${user?.email ?? ''}`,
'',
'Reason for Cancellation:',
'[Please share the reason for cancellation]',
'',
'Additional feedback (optional):',
'[Any other feedback]',
'',
'Regards,',
'[user name or team name]',
].join('\n'),
);
const link = document.createElement('a');
link.href = `mailto:cloud-support@signoz.io?subject=${subject}&body=${body}`;
link.click();
setOpen(false);
};
const footer = (
<>
<Button
variant="solid"
color="secondary"
onClick={(): void => setOpen(false)}
>
Keep Subscription
</Button>
<Button variant="solid" color="destructive" onClick={handleContactSupport}>
Contact Support
</Button>
</>
);
return (
<>
<div className="cancel-subscription-banner">
<div className="cancel-subscription-banner__info">
<span className="cancel-subscription-banner__title">
Cancel Subscription
</span>
<span className="cancel-subscription-banner__subtitle">
Cancel your SigNoz subscription.
</span>
</div>
<Button
variant="solid"
color="destructive"
prefix={<X size={12} />}
onClick={(): void => setOpen(true)}
>
Cancel Subscription
</Button>
</div>
<DialogWrapper
open={open}
onOpenChange={setOpen}
title="Cancel your subscription"
width="narrow"
showCloseButton
footer={footer}
>
<p className="cancel-subscription-dialog__body">
To cancel your SigNoz subscription, please reach out to our support team.
We&apos;ll be happy to assist you.
</p>
</DialogWrapper>
</>
);
}
export default CancelSubscriptionBanner;

View File

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

View File

@@ -370,7 +370,7 @@ function FormAlertRules({
// onQueryCategoryChange handles changes to query category
// in state as well as sets additional defaults
const onQueryCategoryChange = (val: EQueryType): void => {
const element = document.getElementById('top');
const element = document.querySelector('#top');
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -101,7 +101,7 @@ function TracesExplorer(): JSX.Element {
getExplorerViewFromUrl(searchParams, panelTypesFromUrl),
);
const [warning, setWarning] = useState<Warning | undefined>(undefined);
const [warning, setWarning] = useState<Warning | undefined>();
const [isOpen, setOpen] = useState<boolean>(true);
const defaultQuery = useMemo(

2
go.mod
View File

@@ -8,7 +8,7 @@ require (
github.com/ClickHouse/clickhouse-go/v2 v2.40.1
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
github.com/SigNoz/signoz-otel-collector v0.144.3
github.com/SigNoz/signoz-otel-collector v0.144.3-rc.4
github.com/antlr4-go/antlr/v4 v4.13.1
github.com/antonmedv/expr v1.15.3
github.com/cespare/xxhash/v2 v2.3.0

4
go.sum
View File

@@ -108,8 +108,8 @@ github.com/SigNoz/expr v1.17.7-beta h1:FyZkleM5dTQ0O6muQfwGpoH5A2ohmN/XTasRCO72g
github.com/SigNoz/expr v1.17.7-beta/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd h1:Bk43AsDYe0fhkbj57eGXx8H3ZJ4zhmQXBnrW523ktj8=
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc=
github.com/SigNoz/signoz-otel-collector v0.144.3 h1:/7PPIqIpPsaWtrgnfHam2XVYP41ZlgEKLHzQO8oVxcA=
github.com/SigNoz/signoz-otel-collector v0.144.3/go.mod h1:9pLVpcIQvUT88ZiNnZN/MI+XLruvwC+Xm2UzPmGjNfA=
github.com/SigNoz/signoz-otel-collector v0.144.3-rc.4 h1:EskJkEMfuuIyArWhV8SleDV/fuKxiaEGTXrCZIFqDT4=
github.com/SigNoz/signoz-otel-collector v0.144.3-rc.4/go.mod h1:9pLVpcIQvUT88ZiNnZN/MI+XLruvwC+Xm2UzPmGjNfA=
github.com/Yiling-J/theine-go v0.6.2 h1:1GeoXeQ0O0AUkiwj2S9Jc0Mzx+hpqzmqsJ4kIC4M9AY=
github.com/Yiling-J/theine-go v0.6.2/go.mod h1:08QpMa5JZ2pKN+UJCRrCasWYO1IKCdl54Xa836rpmDU=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=

View File

@@ -16,7 +16,7 @@ func (provider *provider) addInfraMonitoringRoutes(router *mux.Router) error {
ID: "ListHosts",
Tags: []string{"inframonitoring"},
Summary: "List Hosts for Infra Monitoring",
Description: "Returns a paginated list of hosts with key infrastructure metrics: CPU usage (%), memory usage (%), I/O wait (%), disk usage (%), and 15-minute load average. Each host includes its current status (active/inactive based on metrics reported in the last 10 minutes) and metadata attributes (e.g., os.type). Supports filtering via a filter expression, filtering by host status, custom groupBy to aggregate hosts by any attribute, ordering by any of the five metrics, and pagination via offset/limit. The response type is 'list' for the default host.name grouping or 'grouped_list' for custom groupBy keys. Also reports missing required metrics and whether the requested time range falls before the data retention boundary.",
Description: "Returns a paginated list of hosts with key infrastructure metrics: CPU usage (%), memory usage (%), I/O wait (%), disk usage (%), and 15-minute load average. Each host includes its current status (active/inactive based on metrics reported in the last 10 minutes) and metadata attributes (e.g., os.type). Supports filtering via a filter expression, filtering by host status, custom groupBy to aggregate hosts by any attribute, ordering by any of the five metrics, and pagination via offset/limit. The response type is 'list' for the default host.name grouping or 'grouped_list' for custom groupBy keys. Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (cpu, memory, wait, load15, diskUsage) return -1 as a sentinel when no data is available for that field; frontends should render '—' rather than the literal value.",
Request: new(inframonitoringtypes.PostableHosts),
RequestContentType: "application/json",
Response: new(inframonitoringtypes.Hosts),
@@ -29,5 +29,42 @@ func (provider *provider) addInfraMonitoringRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v2/infra_monitoring/pods", handler.New(
provider.authZ.ViewAccess(provider.infraMonitoringHandler.ListPods),
handler.OpenAPIDef{
ID: "ListPods",
Tags: []string{"inframonitoring"},
Summary: "List Pods for Infra Monitoring",
Description: "Returns a paginated list of Kubernetes pods with key metrics: CPU usage, CPU request/limit utilization, memory working set, memory request/limit utilization, current pod phase (pending/running/succeeded/failed/unknown), and pod age (ms since start time). Each pod includes metadata attributes (namespace, node, workload owner such as deployment/statefulset/daemonset/job/cronjob, cluster). Supports filtering via a filter expression, custom groupBy to aggregate pods by any attribute, ordering by any of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit. The response type is 'list' for the default k8s.pod.uid grouping or 'grouped_list' for custom groupBy keys. Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (podCPU, podCPURequest, podCPULimit, podMemory, podMemoryRequest, podMemoryLimit, podAge) return -1 as a sentinel when no data is available for that field; frontends should render '—' rather than the literal value.",
Request: new(inframonitoringtypes.PostablePods),
RequestContentType: "application/json",
Response: new(inframonitoringtypes.Pods),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v2/infra_monitoring/onboarding", handler.New(
provider.authZ.ViewAccess(provider.infraMonitoringHandler.GetOnboarding),
handler.OpenAPIDef{
ID: "GetOnboarding",
Tags: []string{"inframonitoring"},
Summary: "Get Onboarding Status for Infra Monitoring",
Description: "Returns the per-tab readiness of the infra-monitoring section selected by the 'type' query parameter (hosts, processes, pods, nodes, deployments, daemonsets, statefulsets, jobs, namespaces, clusters, volumes). For each collector receiver or processor that contributes required metrics or attributes, lists what is present and what is missing, with a prebuilt user-facing message and a docs link per missing component. Default-enabled metrics are those expected as soon as the receiver is configured; optional metrics require 'enabled: true' in receiver config. 'ready' is true only when every missing list is empty.",
RequestQuery: new(inframonitoringtypes.PostableOnboarding),
Response: new(inframonitoringtypes.Onboarding),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -16,8 +16,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/fields"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/preference"
"github.com/SigNoz/signoz/pkg/modules/promote"
@@ -25,7 +25,6 @@ import (
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
@@ -63,7 +62,6 @@ type provider struct {
factoryHandler factory.Handler
cloudIntegrationHandler cloudintegration.Handler
ruleStateHistoryHandler rulestatehistory.Handler
spanMapperHandler spanmapper.Handler
alertmanagerHandler alertmanager.Handler
traceDetailHandler tracedetail.Handler
rulerHandler ruler.Handler
@@ -94,7 +92,6 @@ func NewFactory(
factoryHandler factory.Handler,
cloudIntegrationHandler cloudintegration.Handler,
ruleStateHistoryHandler rulestatehistory.Handler,
spanMapperHandler spanmapper.Handler,
alertmanagerHandler alertmanager.Handler,
traceDetailHandler tracedetail.Handler,
rulerHandler ruler.Handler,
@@ -128,7 +125,6 @@ func NewFactory(
factoryHandler,
cloudIntegrationHandler,
ruleStateHistoryHandler,
spanMapperHandler,
alertmanagerHandler,
traceDetailHandler,
rulerHandler,
@@ -164,7 +160,6 @@ func newProvider(
factoryHandler factory.Handler,
cloudIntegrationHandler cloudintegration.Handler,
ruleStateHistoryHandler rulestatehistory.Handler,
spanMapperHandler spanmapper.Handler,
alertmanagerHandler alertmanager.Handler,
traceDetailHandler tracedetail.Handler,
rulerHandler ruler.Handler,
@@ -198,7 +193,6 @@ func newProvider(
factoryHandler: factoryHandler,
cloudIntegrationHandler: cloudIntegrationHandler,
ruleStateHistoryHandler: ruleStateHistoryHandler,
spanMapperHandler: spanMapperHandler,
alertmanagerHandler: alertmanagerHandler,
traceDetailHandler: traceDetailHandler,
rulerHandler: rulerHandler,
@@ -306,10 +300,6 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
return err
}
if err := provider.addSpanMapperRoutes(router); err != nil {
return err
}
if err := provider.addAlertmanagerRoutes(router); err != nil {
return err
}

View File

@@ -1,171 +0,0 @@
package signozapiserver
import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/gorilla/mux"
)
func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
if err := router.Handle("/api/v1/span_mapper_groups", handler.New(
provider.authZ.ViewAccess(provider.spanMapperHandler.ListGroups),
handler.OpenAPIDef{
ID: "ListSpanMapperGroups",
Tags: []string{"spanmapper"},
Summary: "List span attribute mapping groups",
Description: "Returns all span attribute mapping groups for the authenticated org.",
Request: nil,
RequestContentType: "",
RequestQuery: new(spantypes.ListSpanMapperGroupsQuery),
Response: new(spantypes.GettableSpanMapperGroups),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
},
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.CreateGroup),
handler.OpenAPIDef{
ID: "CreateSpanMapperGroup",
Tags: []string{"spanmapper"},
Summary: "Create a span attribute mapping group",
Description: "Creates a new span attribute mapping group for the org.",
Request: new(spantypes.PostableSpanMapperGroup),
RequestContentType: "application/json",
Response: new(spantypes.GettableSpanMapperGroup),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.UpdateGroup),
handler.OpenAPIDef{
ID: "UpdateSpanMapperGroup",
Tags: []string{"spanmapper"},
Summary: "Update a span attribute mapping group",
Description: "Partially updates an existing mapping group's name, condition, or enabled state.",
Request: new(spantypes.UpdatableSpanMapperGroup),
RequestContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.DeleteGroup),
handler.OpenAPIDef{
ID: "DeleteSpanMapperGroup",
Tags: []string{"spanmapper"},
Summary: "Delete a span attribute mapping group",
Description: "Hard-deletes a mapping group and cascades to all its mappers.",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers", handler.New(
provider.authZ.ViewAccess(provider.spanMapperHandler.ListMappers),
handler.OpenAPIDef{
ID: "ListSpanMappers",
Tags: []string{"spanmapper"},
Summary: "List span mappers for a group",
Description: "Returns all mappers belonging to a mapping group.",
Request: nil,
RequestContentType: "",
Response: new(spantypes.GettableSpanMapperGroups),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
},
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.CreateMapper),
handler.OpenAPIDef{
ID: "CreateSpanMapper",
Tags: []string{"spanmapper"},
Summary: "Create a span mapper",
Description: "Adds a new mapper to the specified mapping group.",
Request: new(spantypes.PostableSpanMapper),
RequestContentType: "application/json",
Response: new(spantypes.GettableSpanMapper),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.UpdateMapper),
handler.OpenAPIDef{
ID: "UpdateSpanMapper",
Tags: []string{"spanmapper"},
Summary: "Update a span mapper",
Description: "Partially updates an existing mapper's field context, config, or enabled state.",
Request: new(spantypes.UpdatableSpanMapper),
RequestContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.DeleteMapper),
handler.OpenAPIDef{
ID: "DeleteSpanMapper",
Tags: []string{"spanmapper"},
Summary: "Delete a span mapper",
Description: "Hard-deletes a mapper from a mapping group.",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -440,4 +440,3 @@ func (handler *handler) AgentCheckIn(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusOK, cloudintegrationtypes.NewGettableAgentCheckIn(provider, resp))
}

View File

@@ -22,6 +22,30 @@ func NewHandler(m inframonitoring.Module) inframonitoring.Handler {
}
}
func (h *handler) GetOnboarding(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context())
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
var parsedReq inframonitoringtypes.PostableOnboarding
if err := binding.Query.BindQuery(req.URL.Query(), &parsedReq); err != nil {
render.Error(rw, err)
return
}
result, err := h.module.GetOnboarding(req.Context(), orgID, &parsedReq)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, result)
}
func (h *handler) ListHosts(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context())
if err != nil {
@@ -45,3 +69,27 @@ func (h *handler) ListHosts(rw http.ResponseWriter, req *http.Request) {
render.Success(rw, http.StatusOK, result)
}
func (h *handler) ListPods(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context())
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
var parsedReq inframonitoringtypes.PostablePods
if err := binding.JSON.BindBody(req.Body, &parsedReq); err != nil {
render.Error(rw, err)
return
}
result, err := h.module.ListPods(req.Context(), orgID, &parsedReq)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, result)
}

View File

@@ -434,6 +434,57 @@ func (m *module) getMetricsExistenceAndEarliestTime(ctx context.Context, metricN
return missingMetrics, globalMinFirstReported, nil
}
// getAttributesExistence returns the subset of attrNames that are missing —
// i.e. have never been reported as a label on any of the given metricNames.
// Presence is checked against distributed_metadata without a time-range filter.
func (m *module) getAttributesExistence(ctx context.Context, metricNames, attrNames []string) ([]string, error) {
if len(attrNames) == 0 {
return nil, nil
}
if len(metricNames) == 0 {
return nil, errors.NewInternalf(errors.CodeInternal, "getAttributesExistence: metricNames must not be empty")
}
sb := sqlbuilder.NewSelectBuilder()
sb.Select("attr_name", "count(*) AS cnt")
sb.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, telemetrymetrics.AttributesMetadataTableName))
sb.Where(
sb.In("metric_name", sqlbuilder.List(metricNames)),
sb.In("attr_name", sqlbuilder.List(attrNames)),
)
sb.GroupBy("attr_name")
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
rows, err := m.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
present := make(map[string]bool, len(attrNames))
for rows.Next() {
var name string
var cnt uint64
if err := rows.Scan(&name, &cnt); err != nil {
return nil, err
}
if name != "" && cnt > 0 {
present[name] = true
}
}
if err := rows.Err(); err != nil {
return nil, err
}
var missing []string
for _, a := range attrNames {
if !present[a] {
missing = append(missing, a)
}
}
return missing, nil
}
// getMetadata fetches the latest values of additionalCols for each unique combination of groupBy keys,
// within the given time range and metric names. It uses argMax(tuple(...), unix_milli) to ensure
// we always pick attribute values from the latest timestamp for each group.

View File

@@ -1,5 +1,7 @@
package implinframonitoring
import "github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
// The types in this file are only used within the implinframonitoring package, and are not exposed outside.
// They are primarily used for internal processing and structuring of data within the module's implementation.
@@ -14,3 +16,50 @@ type groupHostStatusCounts struct {
Active int
Inactive int
}
// bucketSplit carries the up-to-six entries a single spec bucket contributes
// to an onboarding response. Any field may be nil if the bucket doesn't
// populate that dimension.
type bucketSplit struct {
PresentDefault *inframonitoringtypes.MetricsComponentEntry
PresentOptional *inframonitoringtypes.MetricsComponentEntry
PresentAttrs *inframonitoringtypes.AttributesComponentEntry
MissingDefault *inframonitoringtypes.MissingMetricsComponentEntry
MissingOptional *inframonitoringtypes.MissingMetricsComponentEntry
MissingAttrs *inframonitoringtypes.MissingAttributesComponentEntry
}
// onboardingComponentBucket is a single collector component's contribution
// toward a single infra-monitoring tab's readiness. Any of the three dimension
// slices (DefaultMetrics, OptionalMetrics, RequiredAttrs) may be empty — the
// bucketizer in Phase 4 skips empty dimensions.
type onboardingComponentBucket struct {
Component inframonitoringtypes.AssociatedComponent
DefaultMetrics []string
OptionalMetrics []string
RequiredAttrs []string
DocumentationLink string
}
// onboardingSpec defines, for one OnboardingType, the full set of
// component-scoped buckets that must be satisfied for the tab to be ready.
type onboardingSpec struct {
Buckets []onboardingComponentBucket
}
func (s onboardingSpec) getAllMetrics() []string {
var out []string
for _, b := range s.Buckets {
out = append(out, b.DefaultMetrics...)
out = append(out, b.OptionalMetrics...)
}
return out
}
func (s onboardingSpec) getAllAttrs() []string {
var out []string
for _, b := range s.Buckets {
out = append(out, b.RequiredAttrs...)
}
return out
}

View File

@@ -47,6 +47,80 @@ func NewModule(
}
}
// GetOnboarding runs a per-type readiness check: for the requested
// infra-monitoring tab, reports which required metrics and attributes are
// present vs missing, grouped by the collector component that produces them.
// Ready is true iff every missing list is empty.
func (m *module) GetOnboarding(ctx context.Context, orgID valuer.UUID, req *inframonitoringtypes.PostableOnboarding) (*inframonitoringtypes.Onboarding, error) {
if err := req.Validate(); err != nil {
return nil, err
}
spec, err := getSpecForType(req.Type)
if err != nil {
return nil, err
}
allMetrics := spec.getAllMetrics()
allAttrs := spec.getAllAttrs()
missingMetricsList, _, err := m.getMetricsExistenceAndEarliestTime(ctx, allMetrics)
if err != nil {
return nil, err
}
missingMetricsMap := make(map[string]bool, len(missingMetricsList))
for _, name := range missingMetricsList {
missingMetricsMap[name] = true
}
missingAttrsList, err := m.getAttributesExistence(ctx, allMetrics, allAttrs)
if err != nil {
return nil, err
}
missingAttrsMap := make(map[string]bool, len(missingAttrsList))
for _, name := range missingAttrsList {
missingAttrsMap[name] = true
}
resp := &inframonitoringtypes.Onboarding{
Type: req.Type,
PresentDefaultEnabledMetrics: []inframonitoringtypes.MetricsComponentEntry{},
PresentOptionalMetrics: []inframonitoringtypes.MetricsComponentEntry{},
PresentRequiredAttributes: []inframonitoringtypes.AttributesComponentEntry{},
MissingDefaultEnabledMetrics: []inframonitoringtypes.MissingMetricsComponentEntry{},
MissingOptionalMetrics: []inframonitoringtypes.MissingMetricsComponentEntry{},
MissingRequiredAttributes: []inframonitoringtypes.MissingAttributesComponentEntry{},
}
for _, b := range spec.Buckets {
s := splitBucket(b, missingMetricsMap, missingAttrsMap)
if s.PresentDefault != nil {
resp.PresentDefaultEnabledMetrics = append(resp.PresentDefaultEnabledMetrics, *s.PresentDefault)
}
if s.PresentOptional != nil {
resp.PresentOptionalMetrics = append(resp.PresentOptionalMetrics, *s.PresentOptional)
}
if s.PresentAttrs != nil {
resp.PresentRequiredAttributes = append(resp.PresentRequiredAttributes, *s.PresentAttrs)
}
if s.MissingDefault != nil {
resp.MissingDefaultEnabledMetrics = append(resp.MissingDefaultEnabledMetrics, *s.MissingDefault)
}
if s.MissingOptional != nil {
resp.MissingOptionalMetrics = append(resp.MissingOptionalMetrics, *s.MissingOptional)
}
if s.MissingAttrs != nil {
resp.MissingRequiredAttributes = append(resp.MissingRequiredAttributes, *s.MissingAttrs)
}
}
resp.Ready = len(resp.MissingDefaultEnabledMetrics) == 0 &&
len(resp.MissingOptionalMetrics) == 0 &&
len(resp.MissingRequiredAttributes) == 0
return resp, nil
}
func (m *module) ListHosts(ctx context.Context, orgID valuer.UUID, req *inframonitoringtypes.PostableHosts) (*inframonitoringtypes.Hosts, error) {
if err := req.Validate(); err != nil {
return nil, err
@@ -159,3 +233,79 @@ func (m *module) ListHosts(ctx context.Context, orgID valuer.UUID, req *inframon
return resp, nil
}
func (m *module) ListPods(ctx context.Context, orgID valuer.UUID, req *inframonitoringtypes.PostablePods) (*inframonitoringtypes.Pods, error) {
if err := req.Validate(); err != nil {
return nil, err
}
resp := &inframonitoringtypes.Pods{}
if req.OrderBy == nil {
req.OrderBy = &qbtypes.OrderBy{
Key: qbtypes.OrderByKey{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: inframonitoringtypes.PodsOrderByCPU,
},
},
Direction: qbtypes.OrderDirectionDesc,
}
}
if len(req.GroupBy) == 0 {
req.GroupBy = []qbtypes.GroupByKey{podUIDGroupByKey}
resp.Type = inframonitoringtypes.ResponseTypeList
} else {
resp.Type = inframonitoringtypes.ResponseTypeGroupedList
}
missingMetrics, minFirstReportedUnixMilli, err := m.getMetricsExistenceAndEarliestTime(ctx, podsTableMetricNamesList)
if err != nil {
return nil, err
}
if len(missingMetrics) > 0 {
resp.RequiredMetricsCheck = inframonitoringtypes.RequiredMetricsCheck{MissingMetrics: missingMetrics}
resp.Records = []inframonitoringtypes.PodRecord{}
resp.Total = 0
return resp, nil
}
if req.End < int64(minFirstReportedUnixMilli) {
resp.EndTimeBeforeRetention = true
resp.Records = []inframonitoringtypes.PodRecord{}
resp.Total = 0
return resp, nil
}
resp.RequiredMetricsCheck = inframonitoringtypes.RequiredMetricsCheck{MissingMetrics: []string{}}
metadataMap, err := m.getPodsTableMetadata(ctx, req)
if err != nil {
return nil, err
}
resp.Total = len(metadataMap)
pageGroups, err := m.getTopPodGroups(ctx, orgID, req, metadataMap)
if err != nil {
return nil, err
}
if len(pageGroups) == 0 {
resp.Records = []inframonitoringtypes.PodRecord{}
return resp, nil
}
filterExpr := ""
if req.Filter != nil {
filterExpr = req.Filter.Expression
}
fullQueryReq := buildFullQueryRequest(req.Start, req.End, filterExpr, req.GroupBy, pageGroups, m.newPodsTableListQuery())
queryResp, err := m.querier.QueryRange(ctx, orgID, fullQueryReq)
if err != nil {
return nil, err
}
resp.Records = buildPodRecords(queryResp, pageGroups, req.GroupBy, metadataMap, req.End)
resp.Warning = queryResp.Warning
return resp, nil
}

View File

@@ -0,0 +1,114 @@
package implinframonitoring
import (
"fmt"
"strings"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
)
// splitBucket partitions one component bucket's metric and attribute lists
// against the module-wide missing sets into up to six response entries.
// Empty partitions are left nil so callers can skip them.
func splitBucket(b onboardingComponentBucket, missingMetrics, missingAttrs map[string]bool) bucketSplit {
var s bucketSplit
presentDef, missDef := partitionList(b.DefaultMetrics, missingMetrics)
if len(presentDef) > 0 {
s.PresentDefault = &inframonitoringtypes.MetricsComponentEntry{
Metrics: presentDef,
AssociatedComponent: b.Component,
}
}
if len(missDef) > 0 {
s.MissingDefault = &inframonitoringtypes.MissingMetricsComponentEntry{
MetricsComponentEntry: inframonitoringtypes.MetricsComponentEntry{
Metrics: missDef,
AssociatedComponent: b.Component,
},
Message: buildMissingDefaultMetricsMessage(missDef, b.Component.Name),
DocumentationLink: b.DocumentationLink,
}
}
presentOpt, missOpt := partitionList(b.OptionalMetrics, missingMetrics)
if len(presentOpt) > 0 {
s.PresentOptional = &inframonitoringtypes.MetricsComponentEntry{
Metrics: presentOpt,
AssociatedComponent: b.Component,
}
}
if len(missOpt) > 0 {
s.MissingOptional = &inframonitoringtypes.MissingMetricsComponentEntry{
MetricsComponentEntry: inframonitoringtypes.MetricsComponentEntry{
Metrics: missOpt,
AssociatedComponent: b.Component,
},
Message: buildMissingOptionalMetricsMessage(missOpt, b.Component.Name),
DocumentationLink: b.DocumentationLink,
}
}
presentA, missA := partitionList(b.RequiredAttrs, missingAttrs)
if len(presentA) > 0 {
s.PresentAttrs = &inframonitoringtypes.AttributesComponentEntry{
Attributes: presentA,
AssociatedComponent: b.Component,
}
}
if len(missA) > 0 {
s.MissingAttrs = &inframonitoringtypes.MissingAttributesComponentEntry{
AttributesComponentEntry: inframonitoringtypes.AttributesComponentEntry{
Attributes: missA,
AssociatedComponent: b.Component,
},
Message: buildMissingRequiredAttrsMessage(missA, b.Component.Name),
DocumentationLink: b.DocumentationLink,
}
}
return s
}
// getSpecForType returns the onboardingSpec for a given OnboardingType, or an error if the type is invalid.
func getSpecForType(t inframonitoringtypes.OnboardingType) (*onboardingSpec, error) {
spec, ok := onboardingSpecs[t]
if !ok {
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "no onboarding spec for type: %s", t)
}
return &spec, nil
}
// partitionList splits items into those NOT in `missing` and those in `missing`.
// Preserves input order.
func partitionList(items []string, missing map[string]bool) (present, miss []string) {
for _, x := range items {
if missing[x] {
miss = append(miss, x)
} else {
present = append(present, x)
}
}
return present, miss
}
func buildMissingDefaultMetricsMessage(metrics []string, componentName string) string {
return fmt.Sprintf(
"Missing default metrics %s from %s. Learn how to configure here.",
strings.Join(metrics, ", "), componentName,
)
}
func buildMissingOptionalMetricsMessage(metrics []string, componentName string) string {
return fmt.Sprintf(
"Missing optional metrics %s from %s. Learn how to enable here.",
strings.Join(metrics, ", "), componentName,
)
}
func buildMissingRequiredAttrsMessage(attrs []string, componentName string) string {
return fmt.Sprintf(
"Missing required attributes %s from %s. Learn how to configure here.",
strings.Join(attrs, ", "), componentName,
)
}

View File

@@ -0,0 +1,364 @@
package implinframonitoring
import "github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
// Component names — the 5 OTel collector receivers/processors that produce
// metrics and resource attributes consumed by infra-monitoring tabs. Bare
// strings on purpose (not wrapped enums) — the list is open-ended enough that
// an enum adds more friction than value.
const (
componentNameHostMetricsReceiver = "hostmetricsreceiver"
componentNameKubeletStatsReceiver = "kubeletstatsreceiver"
componentNameK8sClusterReceiver = "k8sclusterreceiver"
componentNameResourceDetectionProcessor = "resourcedetectionprocessor"
componentNameK8sAttributesProcessor = "k8sattributesprocessor"
)
// Documentation links — one per component. User-facing; emitted on missing-entries.
const (
docLinkHostMetricsReceiver = "https://signoz.io/docs/infrastructure-monitoring/hostmetrics/#step-2-configure-the-collector"
docLinkKubeletStatsReceiver = "https://signoz.io/docs/infrastructure-monitoring/k8s-metrics/#setting-up-kubelet-stats-monitoring"
docLinkK8sClusterReceiver = "https://signoz.io/docs/infrastructure-monitoring/k8s-metrics/#setting-up-k8s-cluster-monitoring"
docLinkResourceDetectionProcessor = "https://signoz.io/docs/infrastructure-monitoring/hostmetrics/#host-name-is-blankempty"
docLinkK8sAttributesProcessor = "https://signoz.io/docs/infrastructure-monitoring/k8s-metrics/#2-enable-kubernetes-metadata"
)
var (
componentHostMetricsReceiver = inframonitoringtypes.AssociatedComponent{
Type: inframonitoringtypes.OnboardingComponentTypeReceiver,
Name: componentNameHostMetricsReceiver,
}
componentKubeletStatsReceiver = inframonitoringtypes.AssociatedComponent{
Type: inframonitoringtypes.OnboardingComponentTypeReceiver,
Name: componentNameKubeletStatsReceiver,
}
componentK8sClusterReceiver = inframonitoringtypes.AssociatedComponent{
Type: inframonitoringtypes.OnboardingComponentTypeReceiver,
Name: componentNameK8sClusterReceiver,
}
componentResourceDetectionProcessor = inframonitoringtypes.AssociatedComponent{
Type: inframonitoringtypes.OnboardingComponentTypeProcessor,
Name: componentNameResourceDetectionProcessor,
}
componentK8sAttributesProcessor = inframonitoringtypes.AssociatedComponent{
Type: inframonitoringtypes.OnboardingComponentTypeProcessor,
Name: componentNameK8sAttributesProcessor,
}
)
// onboardingSpecs is the single lookup table the module consults for a type's
// readiness contract. Every OnboardingType value must have an entry here.
var onboardingSpecs = map[inframonitoringtypes.OnboardingType]onboardingSpec{
inframonitoringtypes.OnboardingTypeHosts: hostsSpec,
inframonitoringtypes.OnboardingTypeProcesses: processesSpec,
inframonitoringtypes.OnboardingTypePods: podsSpec,
inframonitoringtypes.OnboardingTypeNodes: nodesSpec,
inframonitoringtypes.OnboardingTypeDeployments: deploymentsSpec,
inframonitoringtypes.OnboardingTypeDaemonsets: daemonsetsSpec,
inframonitoringtypes.OnboardingTypeStatefulsets: statefulsetsSpec,
inframonitoringtypes.OnboardingTypeJobs: jobsSpec,
inframonitoringtypes.OnboardingTypeNamespaces: namespacesSpec,
inframonitoringtypes.OnboardingTypeClusters: clustersSpec,
inframonitoringtypes.OnboardingTypeVolumes: volumesSpec,
}
// Per-type specs. Every metric and attribute is spelled out in its own spec
// on purpose — no shared slices, no concatenation helpers. Repetition is
// cheaper than indirection when auditing what each tab actually requires.
var hostsSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentHostMetricsReceiver,
DefaultMetrics: []string{
"system.cpu.time",
"system.memory.usage",
"system.cpu.load_average.15m",
"system.filesystem.usage",
},
DocumentationLink: docLinkHostMetricsReceiver,
},
{
Component: componentResourceDetectionProcessor,
RequiredAttrs: []string{"host.name"},
DocumentationLink: docLinkResourceDetectionProcessor,
},
},
}
var processesSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentHostMetricsReceiver,
DefaultMetrics: []string{
"process.cpu.time",
"process.memory.usage",
},
RequiredAttrs: []string{"process.pid"},
DocumentationLink: docLinkHostMetricsReceiver,
},
},
}
var podsSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentKubeletStatsReceiver,
DefaultMetrics: []string{
"k8s.pod.cpu.usage",
"k8s.pod.memory.working_set",
},
OptionalMetrics: []string{
"k8s.pod.cpu_request_utilization",
"k8s.pod.cpu_limit_utilization",
"k8s.pod.memory_request_utilization",
"k8s.pod.memory_limit_utilization",
},
DocumentationLink: docLinkKubeletStatsReceiver,
},
{
Component: componentK8sClusterReceiver,
DefaultMetrics: []string{"k8s.pod.phase"},
DocumentationLink: docLinkK8sClusterReceiver,
},
{
Component: componentK8sAttributesProcessor,
RequiredAttrs: []string{"k8s.pod.uid"},
DocumentationLink: docLinkK8sAttributesProcessor,
},
},
}
var nodesSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentKubeletStatsReceiver,
DefaultMetrics: []string{
"k8s.node.cpu.usage",
"k8s.node.memory.working_set",
},
DocumentationLink: docLinkKubeletStatsReceiver,
},
{
Component: componentK8sClusterReceiver,
DefaultMetrics: []string{
"k8s.node.allocatable_cpu",
"k8s.node.allocatable_memory", // k8s.node.allocatable_cpu and k8s.node.allocatable_memory are
// controlled by allocatable_types_to_report config option (Check // https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/4f9a578b210a6dcb9f9bf47942f27208b5765298/receiver/k8sclusterreceiver/metadata.yaml#L805-L806)
"k8s.node.condition_ready", // # k8s.node.condition_* metrics (k8s.node.condition_ready, k8s.node.condition_memory_pressure, etc) are controlled# by node_conditions_to_report config option.
// By default, only k8s.node.condition_ready is enabled. (Check https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/4f9a578b210a6dcb9f9bf47942f27208b5765298/receiver/k8sclusterreceiver/metadata.yaml#L802)
},
DocumentationLink: docLinkK8sClusterReceiver,
},
{
Component: componentK8sAttributesProcessor,
RequiredAttrs: []string{"k8s.node.name"},
DocumentationLink: docLinkK8sAttributesProcessor,
},
},
}
var deploymentsSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentKubeletStatsReceiver,
DefaultMetrics: []string{
"k8s.pod.cpu.usage",
"k8s.pod.memory.working_set",
},
OptionalMetrics: []string{
"k8s.pod.cpu_request_utilization",
"k8s.pod.cpu_limit_utilization",
"k8s.pod.memory_request_utilization",
"k8s.pod.memory_limit_utilization",
},
DocumentationLink: docLinkKubeletStatsReceiver,
},
{
Component: componentK8sClusterReceiver,
DefaultMetrics: []string{
"k8s.container.restarts",
"k8s.deployment.desired",
"k8s.deployment.available",
},
DocumentationLink: docLinkK8sClusterReceiver,
},
{
Component: componentK8sAttributesProcessor,
RequiredAttrs: []string{"k8s.deployment.name"},
DocumentationLink: docLinkK8sAttributesProcessor,
},
},
}
var daemonsetsSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentKubeletStatsReceiver,
DefaultMetrics: []string{
"k8s.pod.cpu.usage",
"k8s.pod.memory.working_set",
},
OptionalMetrics: []string{
"k8s.pod.cpu_request_utilization",
"k8s.pod.cpu_limit_utilization",
"k8s.pod.memory_request_utilization",
"k8s.pod.memory_limit_utilization",
},
DocumentationLink: docLinkKubeletStatsReceiver,
},
{
Component: componentK8sClusterReceiver,
DefaultMetrics: []string{
"k8s.container.restarts",
"k8s.daemonset.desired_scheduled_nodes",
"k8s.daemonset.current_scheduled_nodes",
},
DocumentationLink: docLinkK8sClusterReceiver,
},
{
Component: componentK8sAttributesProcessor,
RequiredAttrs: []string{"k8s.daemonset.name"},
DocumentationLink: docLinkK8sAttributesProcessor,
},
},
}
var statefulsetsSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentKubeletStatsReceiver,
DefaultMetrics: []string{
"k8s.pod.cpu.usage",
"k8s.pod.memory.working_set",
},
OptionalMetrics: []string{
"k8s.pod.cpu_request_utilization",
"k8s.pod.cpu_limit_utilization",
"k8s.pod.memory_request_utilization",
"k8s.pod.memory_limit_utilization",
},
DocumentationLink: docLinkKubeletStatsReceiver,
},
{
Component: componentK8sClusterReceiver,
DefaultMetrics: []string{
"k8s.container.restarts",
"k8s.statefulset.desired_pods",
"k8s.statefulset.current_pods",
},
DocumentationLink: docLinkK8sClusterReceiver,
},
{
Component: componentK8sAttributesProcessor,
RequiredAttrs: []string{"k8s.statefulset.name"},
DocumentationLink: docLinkK8sAttributesProcessor,
},
},
}
var jobsSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentKubeletStatsReceiver,
DefaultMetrics: []string{
"k8s.pod.cpu.usage",
"k8s.pod.memory.working_set",
},
OptionalMetrics: []string{
"k8s.pod.cpu_request_utilization",
"k8s.pod.cpu_limit_utilization",
"k8s.pod.memory_request_utilization",
"k8s.pod.memory_limit_utilization",
},
DocumentationLink: docLinkKubeletStatsReceiver,
},
{
Component: componentK8sClusterReceiver,
DefaultMetrics: []string{
"k8s.container.restarts",
"k8s.job.desired_successful_pods",
"k8s.job.active_pods",
"k8s.job.failed_pods",
"k8s.job.successful_pods",
},
DocumentationLink: docLinkK8sClusterReceiver,
},
{
Component: componentK8sAttributesProcessor,
RequiredAttrs: []string{"k8s.job.name"},
DocumentationLink: docLinkK8sAttributesProcessor,
},
},
}
var namespacesSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentKubeletStatsReceiver,
DefaultMetrics: []string{
"k8s.pod.cpu.usage",
"k8s.pod.memory.working_set",
},
DocumentationLink: docLinkKubeletStatsReceiver,
},
{
Component: componentK8sClusterReceiver,
DefaultMetrics: []string{"k8s.pod.phase"},
DocumentationLink: docLinkK8sClusterReceiver,
},
{
Component: componentK8sAttributesProcessor,
RequiredAttrs: []string{"k8s.namespace.name"},
DocumentationLink: docLinkK8sAttributesProcessor,
},
},
}
var clustersSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentKubeletStatsReceiver,
DefaultMetrics: []string{
"k8s.node.cpu.usage",
"k8s.node.memory.working_set",
},
DocumentationLink: docLinkKubeletStatsReceiver,
},
{
Component: componentK8sClusterReceiver,
DefaultMetrics: []string{
"k8s.node.allocatable_cpu",
"k8s.node.allocatable_memory", //k8s.node.allocatable_cpu and k8s.node.allocatable_memory are
// controlled by allocatable_types_to_report config option (Check // https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/4f9a578b210a6dcb9f9bf47942f27208b5765298/receiver/k8sclusterreceiver/metadata.yaml#L805-L806)
},
DocumentationLink: docLinkK8sClusterReceiver,
},
{
Component: componentResourceDetectionProcessor,
RequiredAttrs: []string{"k8s.cluster.name"},
DocumentationLink: docLinkResourceDetectionProcessor,
},
},
}
var volumesSpec = onboardingSpec{
Buckets: []onboardingComponentBucket{
{
Component: componentKubeletStatsReceiver,
DefaultMetrics: []string{
"k8s.volume.available",
"k8s.volume.capacity",
"k8s.volume.inodes",
"k8s.volume.inodes.free",
"k8s.volume.inodes.used",
},
DocumentationLink: docLinkKubeletStatsReceiver,
},
{
Component: componentK8sAttributesProcessor,
RequiredAttrs: []string{"k8s.persistentvolumeclaim.name"},
DocumentationLink: docLinkK8sAttributesProcessor,
},
},
}

View File

@@ -0,0 +1,246 @@
package implinframonitoring
import (
"testing"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
"github.com/stretchr/testify/require"
)
// Component used across splitBucket cases — it's a processor so the test
// doesn't carry any receiver semantics.
var testComponent = inframonitoringtypes.AssociatedComponent{
Type: inframonitoringtypes.OnboardingComponentTypeReceiver,
Name: "testreceiver",
}
const testDocLink = "https://example.com/docs"
func TestSplitBucket(t *testing.T) {
type want struct {
presentDefault []string
presentOptional []string
presentAttrs []string
missingDefault []string
missingOptional []string
missingAttrs []string
}
tests := []struct {
name string
bucket onboardingComponentBucket
missingMetrics map[string]bool
missingAttrs map[string]bool
want want
}{
{
name: "empty bucket — nothing to emit",
bucket: onboardingComponentBucket{Component: testComponent, DocumentationLink: testDocLink},
missingMetrics: map[string]bool{},
missingAttrs: map[string]bool{},
want: want{},
},
{
name: "all default metrics present",
bucket: onboardingComponentBucket{
Component: testComponent,
DefaultMetrics: []string{"m1", "m2"},
DocumentationLink: testDocLink,
},
missingMetrics: map[string]bool{},
missingAttrs: map[string]bool{},
want: want{
presentDefault: []string{"m1", "m2"},
},
},
{
name: "all default metrics missing",
bucket: onboardingComponentBucket{
Component: testComponent,
DefaultMetrics: []string{"m1", "m2"},
DocumentationLink: testDocLink,
},
missingMetrics: map[string]bool{"m1": true, "m2": true},
missingAttrs: map[string]bool{},
want: want{
missingDefault: []string{"m1", "m2"},
},
},
{
name: "mixed default metrics",
bucket: onboardingComponentBucket{
Component: testComponent,
DefaultMetrics: []string{"m1", "m2", "m3"},
DocumentationLink: testDocLink,
},
missingMetrics: map[string]bool{"m2": true},
missingAttrs: map[string]bool{},
want: want{
presentDefault: []string{"m1", "m3"},
missingDefault: []string{"m2"},
},
},
{
name: "only optional metrics — all missing",
bucket: onboardingComponentBucket{
Component: testComponent,
OptionalMetrics: []string{"opt1", "opt2"},
DocumentationLink: testDocLink,
},
missingMetrics: map[string]bool{"opt1": true, "opt2": true},
missingAttrs: map[string]bool{},
want: want{
missingOptional: []string{"opt1", "opt2"},
},
},
{
name: "only required attrs — all present",
bucket: onboardingComponentBucket{
Component: testComponent,
RequiredAttrs: []string{"a1", "a2"},
DocumentationLink: testDocLink,
},
missingMetrics: map[string]bool{},
missingAttrs: map[string]bool{},
want: want{
presentAttrs: []string{"a1", "a2"},
},
},
{
name: "only required attrs — all missing",
bucket: onboardingComponentBucket{
Component: testComponent,
RequiredAttrs: []string{"a1"},
DocumentationLink: testDocLink,
},
missingMetrics: map[string]bool{},
missingAttrs: map[string]bool{"a1": true},
want: want{
missingAttrs: []string{"a1"},
},
},
{
name: "every dimension populated on both sides",
bucket: onboardingComponentBucket{
Component: testComponent,
DefaultMetrics: []string{"d1", "d2"},
OptionalMetrics: []string{"o1", "o2"},
RequiredAttrs: []string{"a1", "a2"},
DocumentationLink: testDocLink,
},
missingMetrics: map[string]bool{"d2": true, "o1": true},
missingAttrs: map[string]bool{"a2": true},
want: want{
presentDefault: []string{"d1"},
missingDefault: []string{"d2"},
presentOptional: []string{"o2"},
missingOptional: []string{"o1"},
presentAttrs: []string{"a1"},
missingAttrs: []string{"a2"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := splitBucket(tt.bucket, tt.missingMetrics, tt.missingAttrs)
requireMetricsEntry(t, "presentDefault", got.PresentDefault, tt.want.presentDefault, false)
requireMetricsEntry(t, "presentOptional", got.PresentOptional, tt.want.presentOptional, false)
requireAttrsEntry(t, "presentAttrs", got.PresentAttrs, tt.want.presentAttrs, false)
requireMissingMetrics(t, "missingDefault", got.MissingDefault, tt.want.missingDefault)
requireMissingMetrics(t, "missingOptional", got.MissingOptional, tt.want.missingOptional)
requireMissingAttrs(t, "missingAttrs", got.MissingAttrs, tt.want.missingAttrs)
})
}
}
func TestPartitionList(t *testing.T) {
present, missing := partitionList(
[]string{"a", "b", "c", "d"},
map[string]bool{"b": true, "d": true},
)
require.Equal(t, []string{"a", "c"}, present)
require.Equal(t, []string{"b", "d"}, missing)
}
func TestMissingMessageTemplates(t *testing.T) {
require.Equal(t,
"Missing default metrics m1, m2 from comp. Learn how to configure here.",
buildMissingDefaultMetricsMessage([]string{"m1", "m2"}, "comp"),
)
require.Equal(t,
"Missing optional metrics m1 from comp. Learn how to enable here.",
buildMissingOptionalMetricsMessage([]string{"m1"}, "comp"),
)
require.Equal(t,
"Missing required attributes a1 from comp. Learn how to configure here.",
buildMissingRequiredAttrsMessage([]string{"a1"}, "comp"),
)
require.Equal(t,
"Missing required attributes a1, a2 from comp. Learn how to configure here.",
buildMissingRequiredAttrsMessage([]string{"a1", "a2"}, "comp"),
)
}
// TestOnboardingSpecs_CoverAllTypes ensures the spec map has an entry for
// every OnboardingType — prevents silently shipping an onboarding type that
// has no spec and would 500 at runtime.
func TestOnboardingSpecs_CoverAllTypes(t *testing.T) {
for _, tp := range inframonitoringtypes.ValidOnboardingTypes {
_, ok := onboardingSpecs[tp]
require.True(t, ok, "missing onboarding spec for type %s", tp)
}
require.Len(t, onboardingSpecs, len(inframonitoringtypes.ValidOnboardingTypes))
}
// --- helpers ---
func requireMetricsEntry(t *testing.T, name string, got *inframonitoringtypes.MetricsComponentEntry, wantMetrics []string, _ bool) {
t.Helper()
if len(wantMetrics) == 0 {
require.Nil(t, got, name)
return
}
require.NotNil(t, got, name)
require.Equal(t, wantMetrics, got.Metrics, name)
require.Equal(t, testComponent, got.AssociatedComponent, name)
}
func requireAttrsEntry(t *testing.T, name string, got *inframonitoringtypes.AttributesComponentEntry, wantAttrs []string, _ bool) {
t.Helper()
if len(wantAttrs) == 0 {
require.Nil(t, got, name)
return
}
require.NotNil(t, got, name)
require.Equal(t, wantAttrs, got.Attributes, name)
require.Equal(t, testComponent, got.AssociatedComponent, name)
}
func requireMissingMetrics(t *testing.T, name string, got *inframonitoringtypes.MissingMetricsComponentEntry, wantMetrics []string) {
t.Helper()
if len(wantMetrics) == 0 {
require.Nil(t, got, name)
return
}
require.NotNil(t, got, name)
require.Equal(t, wantMetrics, got.Metrics, name)
require.Equal(t, testComponent, got.AssociatedComponent, name)
require.NotEmpty(t, got.Message, name)
require.Equal(t, testDocLink, got.DocumentationLink, name)
}
func requireMissingAttrs(t *testing.T, name string, got *inframonitoringtypes.MissingAttributesComponentEntry, wantAttrs []string) {
t.Helper()
if len(wantAttrs) == 0 {
require.Nil(t, got, name)
return
}
require.NotNil(t, got, name)
require.Equal(t, wantAttrs, got.Attributes, name)
require.Equal(t, testComponent, got.AssociatedComponent, name)
require.NotEmpty(t, got.Message, name)
require.Equal(t, testDocLink, got.DocumentationLink, name)
}

View File

@@ -0,0 +1,160 @@
package implinframonitoring
import (
"context"
"slices"
"time"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
)
func mapPhaseNumToString(v float64) inframonitoringtypes.PodPhase {
switch int(v) {
case 1:
return inframonitoringtypes.PodPhasePending
case 2:
return inframonitoringtypes.PodPhaseRunning
case 3:
return inframonitoringtypes.PodPhaseSucceeded
case 4:
return inframonitoringtypes.PodPhaseFailed
case 5:
return inframonitoringtypes.PodPhaseUnknown
default:
return inframonitoringtypes.PodPhaseNone
}
}
func (m *module) getTopPodGroups(
ctx context.Context,
orgID valuer.UUID,
req *inframonitoringtypes.PostablePods,
metadataMap map[string]map[string]string,
) ([]map[string]string, error) {
orderByKey := req.OrderBy.Key.Name
queryNamesForOrderBy := orderByToPodsQueryNames[orderByKey]
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
topReq := &qbtypes.QueryRangeRequest{
Start: uint64(req.Start),
End: uint64(req.End),
RequestType: qbtypes.RequestTypeScalar,
CompositeQuery: qbtypes.CompositeQuery{
Queries: make([]qbtypes.QueryEnvelope, 0, len(queryNamesForOrderBy)),
},
}
for _, envelope := range m.newPodsTableListQuery().CompositeQuery.Queries {
if !slices.Contains(queryNamesForOrderBy, envelope.GetQueryName()) {
continue
}
copied := envelope
if copied.Type == qbtypes.QueryTypeBuilder {
existingExpr := ""
if f := copied.GetFilter(); f != nil {
existingExpr = f.Expression
}
reqFilterExpr := ""
if req.Filter != nil {
reqFilterExpr = req.Filter.Expression
}
merged := mergeFilterExpressions(existingExpr, reqFilterExpr)
copied.SetFilter(&qbtypes.Filter{Expression: merged})
copied.SetGroupBy(req.GroupBy)
}
topReq.CompositeQuery.Queries = append(topReq.CompositeQuery.Queries, copied)
}
resp, err := m.querier.QueryRange(ctx, orgID, topReq)
if err != nil {
return nil, err
}
allMetricGroups := parseAndSortGroups(resp, rankingQueryName, req.GroupBy, req.OrderBy.Direction)
return paginateWithBackfill(allMetricGroups, metadataMap, req.GroupBy, req.Offset, req.Limit), nil
}
func (m *module) getPodsTableMetadata(ctx context.Context, req *inframonitoringtypes.PostablePods) (map[string]map[string]string, error) {
var nonGroupByAttrs []string
for _, key := range podAttrKeysForMetadata {
if !isKeyInGroupByAttrs(req.GroupBy, key) {
nonGroupByAttrs = append(nonGroupByAttrs, key)
}
}
return m.getMetadata(ctx, podsTableMetricNamesList, req.GroupBy, nonGroupByAttrs, req.Filter, req.Start, req.End)
}
func buildPodRecords(
resp *qbtypes.QueryRangeResponse,
pageGroups []map[string]string,
groupBy []qbtypes.GroupByKey,
metadataMap map[string]map[string]string,
reqEnd int64,
) []inframonitoringtypes.PodRecord {
metricsMap := parseFullQueryResponse(resp, groupBy)
records := make([]inframonitoringtypes.PodRecord, 0, len(pageGroups))
for _, labels := range pageGroups {
compositeKey := compositeKeyFromLabels(labels, groupBy)
podUID := labels[podUIDAttrKey]
record := inframonitoringtypes.PodRecord{ // initialize with default values
PodUID: podUID,
PodPhase: inframonitoringtypes.PodPhaseNone,
PodCPU: -1,
PodCPURequest: -1,
PodCPULimit: -1,
PodMemory: -1,
PodMemoryRequest: -1,
PodMemoryLimit: -1,
PodAge: -1,
Meta: map[string]any{},
}
if metrics, ok := metricsMap[compositeKey]; ok {
if v, exists := metrics["A"]; exists {
record.PodCPU = v
}
if v, exists := metrics["B"]; exists {
record.PodCPURequest = v
}
if v, exists := metrics["C"]; exists {
record.PodCPULimit = v
}
if v, exists := metrics["D"]; exists {
record.PodMemory = v
}
if v, exists := metrics["E"]; exists {
record.PodMemoryRequest = v
}
if v, exists := metrics["F"]; exists {
record.PodMemoryLimit = v
}
if v, exists := metrics["G"]; exists {
record.PodPhase = mapPhaseNumToString(v)
}
}
if attrs, ok := metadataMap[compositeKey]; ok && isKeyInGroupByAttrs(groupBy, podUID) {
// the condition above ensures we deduce age only if pod uid is in group by because if
// it's not in group by then we might have multiple pod uids in the same group and hence then podAge wont make sense
if startTimeStr, exists := attrs[podStartTimeAttrKey]; exists && startTimeStr != "" {
if t, err := time.Parse(time.RFC3339, startTimeStr); err == nil {
startTimeMs := t.UnixMilli()
if startTimeMs > 0 {
record.PodAge = reqEnd - startTimeMs
}
}
}
for k, v := range attrs {
record.Meta[k] = v
}
}
records = append(records, record)
}
return records
}

View File

@@ -0,0 +1,194 @@
package implinframonitoring
import (
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
)
const (
podUIDAttrKey = "k8s.pod.uid"
podStartTimeAttrKey = "k8s.pod.start_time"
)
var podUIDGroupByKey = qbtypes.GroupByKey{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: podUIDAttrKey,
FieldContext: telemetrytypes.FieldContextResource,
FieldDataType: telemetrytypes.FieldDataTypeString,
},
}
var podsTableMetricNamesList = []string{
"k8s.pod.cpu.usage",
"k8s.pod.cpu_request_utilization",
"k8s.pod.cpu_limit_utilization",
"k8s.pod.memory.working_set",
"k8s.pod.memory_request_utilization",
"k8s.pod.memory_limit_utilization",
"k8s.pod.phase",
}
var podAttrKeysForMetadata = []string{
"k8s.pod.uid",
"k8s.pod.name",
"k8s.namespace.name",
"k8s.node.name",
"k8s.deployment.name",
"k8s.statefulset.name",
"k8s.daemonset.name",
"k8s.job.name",
"k8s.cronjob.name",
"k8s.cluster.name",
"k8s.pod.start_time",
}
var orderByToPodsQueryNames = map[string][]string{
inframonitoringtypes.PodsOrderByCPU: {"A"},
inframonitoringtypes.PodsOrderByCPURequest: {"B"},
inframonitoringtypes.PodsOrderByCPULimit: {"C"},
inframonitoringtypes.PodsOrderByMemory: {"D"},
inframonitoringtypes.PodsOrderByMemoryRequest: {"E"},
inframonitoringtypes.PodsOrderByMemoryLimit: {"F"},
}
func (m *module) newPodsTableListQuery() *qbtypes.QueryRangeRequest {
return &qbtypes.QueryRangeRequest{
RequestType: qbtypes.RequestTypeScalar,
CompositeQuery: qbtypes.CompositeQuery{
Queries: []qbtypes.QueryEnvelope{
// Query A: CPU usage
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "A",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "k8s.pod.cpu.usage",
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{podUIDGroupByKey},
Disabled: false,
},
},
// Query B: CPU request utilization
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "B",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "k8s.pod.cpu_request_utilization",
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationAvg,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{podUIDGroupByKey},
Disabled: false,
},
},
// Query C: CPU limit utilization
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "C",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "k8s.pod.cpu_limit_utilization",
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationAvg,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{podUIDGroupByKey},
Disabled: false,
},
},
// Query D: Memory working set
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "D",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "k8s.pod.memory.working_set",
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationSum,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{podUIDGroupByKey},
Disabled: false,
},
},
// Query E: Memory request utilization
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "E",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "k8s.pod.memory_request_utilization",
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationAvg,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{podUIDGroupByKey},
Disabled: false,
},
},
// Query F: Memory limit utilization
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "F",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "k8s.pod.memory_limit_utilization",
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationAvg,
ReduceTo: qbtypes.ReduceToAvg,
},
},
GroupBy: []qbtypes.GroupByKey{podUIDGroupByKey},
Disabled: false,
},
},
// Query G: Pod phase (latest value per pod)
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "G",
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "k8s.pod.phase",
TimeAggregation: metrictypes.TimeAggregationLatest,
SpaceAggregation: metrictypes.SpaceAggregationMax, // Space aggregation here should ideally have been something
// like spaceaggregation last to pick up the last seen phase of the pod in case of more than one fingerprint for a pod uid for pod phase.
// but since the chance of that happening is very low and we don't have spaceaggregation last,
// we have picked max which will give us the correct phase in majority of cases and in worst case will give us the most severe phase in case of multiple fingerprints for a pod uid for pod phase.
// TODO(nikhilmantri0902): Add spaceaggregation last once supported and use that here.
ReduceTo: qbtypes.ReduceToLast,
},
},
GroupBy: []qbtypes.GroupByKey{podUIDGroupByKey},
Disabled: false,
},
},
},
},
}
}

View File

@@ -10,8 +10,12 @@ import (
type Handler interface {
ListHosts(http.ResponseWriter, *http.Request)
ListPods(http.ResponseWriter, *http.Request)
GetOnboarding(http.ResponseWriter, *http.Request)
}
type Module interface {
ListHosts(ctx context.Context, orgID valuer.UUID, req *inframonitoringtypes.PostableHosts) (*inframonitoringtypes.Hosts, error)
ListPods(ctx context.Context, orgID valuer.UUID, req *inframonitoringtypes.PostablePods) (*inframonitoringtypes.Pods, error)
GetOnboarding(ctx context.Context, orgID valuer.UUID, req *inframonitoringtypes.PostableOnboarding) (*inframonitoringtypes.Onboarding, error)
}

View File

@@ -32,17 +32,27 @@ func NewModule(metadataStore telemetrytypes.MetadataStore, telemetrystore teleme
}
func (m *module) ListPromotedAndIndexedPaths(ctx context.Context) ([]promotetypes.PromotePath, error) {
indexes, err := m.metadataStore.ListLogsJSONIndexes(ctx)
logsIndexes, err := m.metadataStore.ListLogsJSONIndexes(ctx)
if err != nil {
return nil, err
}
// Flatten the map values (which are slices) into a single slice
indexes := slices.Concat(slices.Collect(maps.Values(logsIndexes))...)
aggr := map[string][]promotetypes.WrappedIndex{}
for _, index := range indexes {
aggr[index.Name] = append(aggr[index.Name], promotetypes.WrappedIndex{
FieldDataType: index.FieldDataType,
Type: index.IndexType,
Granularity: index.Granularity,
path, columnType, err := schemamigrator.UnfoldJSONSubColumnIndexExpr(index.Expression)
if err != nil {
return nil, err
}
// clean backticks from the path
path = strings.ReplaceAll(path, "`", "")
aggr[path] = append(aggr[path], promotetypes.WrappedIndex{
ColumnType: columnType,
Type: index.Type,
Granularity: index.Granularity,
})
}
promotedPaths, err := m.listPromotedPaths(ctx)

View File

@@ -1,301 +0,0 @@
package implspanmapper
import (
"context"
"net/http"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/http/binding"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
type handler struct {
module spanmapper.Module
providerSettings factory.ProviderSettings
}
func NewHandler(module spanmapper.Module, providerSettings factory.ProviderSettings) spanmapper.Handler {
return &handler{module: module, providerSettings: providerSettings}
}
// ListGroups handles GET /api/v1/span_mapper_groups.
func (h *handler) ListGroups(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
var q spantypes.ListSpanMapperGroupsQuery
if err := binding.Query.BindQuery(r.URL.Query(), &q); err != nil {
render.Error(rw, err)
return
}
groups, err := h.module.ListGroups(ctx, orgID, &q)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, spantypes.NewGettableSpanMapperGroups(groups))
}
// CreateGroup handles POST /api/v1/span_mapper_groups.
func (h *handler) CreateGroup(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
req := new(spantypes.PostableSpanMapperGroup)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
group := spantypes.NewSpanMapperGroupFromPostable(req)
err = h.module.CreateGroup(ctx, orgID, claims.Email, group)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusCreated, group)
}
// UpdateGroup handles PUT /api/v1/span_mapper_groups/{id}.
func (h *handler) UpdateGroup(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
id, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
req := new(spantypes.UpdatableSpanMapperGroup)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
err = h.module.UpdateGroup(ctx, orgID, id, claims.Email, spantypes.NewSpanMapperGroupFromUpdatable(req))
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
// DeleteGroup handles DELETE /api/v1/span_mapper_groups/{id}.
func (h *handler) DeleteGroup(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
id, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
if err := h.module.DeleteGroup(ctx, orgID, id); err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
// ListMappers handles GET /api/v1/span_mapper_groups/{id}/span_mappers.
func (h *handler) ListMappers(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
groupID, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
mappers, err := h.module.ListMappers(ctx, orgID, groupID)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, spantypes.NewGettableSpanMappers(mappers))
}
// CreateMapper handles POST /api/v1/span_mapper_groups/{id}/span_mappers.
func (h *handler) CreateMapper(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
groupID, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
req := new(spantypes.PostableSpanMapper)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
mapper := spantypes.NewSpanMapperFromPostable(req)
err = h.module.CreateMapper(ctx, orgID, groupID, claims.Email, mapper)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusCreated, mapper)
}
// UpdateMapper handles PUT /api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}.
func (h *handler) UpdateMapper(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
groupID, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
mapperID, err := mapperIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
req := new(spantypes.UpdatableSpanMapper)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
err = h.module.UpdateMapper(ctx, orgID, groupID, mapperID, claims.Email, spantypes.NewSpanMapperFromUpdatable(req))
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
// DeleteMapper handles DELETE /api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}.
func (h *handler) DeleteMapper(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
groupID, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
mapperID, err := mapperIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
if err := h.module.DeleteMapper(ctx, orgID, groupID, mapperID); err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
// groupIDFromPath extracts and validates the {id} or {groupId} path variable.
func groupIDFromPath(r *http.Request) (valuer.UUID, error) {
vars := mux.Vars(r)
raw := vars["groupId"]
id, err := valuer.NewUUID(raw)
if err != nil {
return valuer.UUID{}, errors.Wrapf(err, errors.TypeInvalidInput, spantypes.ErrCodeMappingInvalidInput, "group id is not a valid uuid")
}
return id, nil
}
// mapperIDFromPath extracts and validates the {mapperId} path variable.
func mapperIDFromPath(r *http.Request) (valuer.UUID, error) {
raw := mux.Vars(r)["mapperId"]
id, err := valuer.NewUUID(raw)
if err != nil {
return valuer.UUID{}, errors.Wrapf(err, errors.TypeInvalidInput, spantypes.ErrCodeMappingInvalidInput, "mapper id is not a valid uuid")
}
return id, nil
}

View File

@@ -1,41 +0,0 @@
package spanmapper
import (
"context"
"net/http"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
// Module defines the business logic for span attribute mapping groups and mappers.
type Module interface {
// Group operations
ListGroups(ctx context.Context, orgID valuer.UUID, q *spantypes.ListSpanMapperGroupsQuery) ([]*spantypes.SpanMapperGroup, error)
GetGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*spantypes.SpanMapperGroup, error)
CreateGroup(ctx context.Context, orgID valuer.UUID, createdBy string, group *spantypes.SpanMapperGroup) error
UpdateGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, group *spantypes.SpanMapperGroup) error
DeleteGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
// Mapper operations
ListMappers(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID) ([]*spantypes.SpanMapper, error)
GetMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID) (*spantypes.SpanMapper, error)
CreateMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, createdBy string, mapper *spantypes.SpanMapper) error
UpdateMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID, updatedBy string, mapper *spantypes.SpanMapper) error
DeleteMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID) error
}
// Handler defines the HTTP handler interface for mapping group and mapper endpoints.
type Handler interface {
// Group handlers
ListGroups(rw http.ResponseWriter, r *http.Request)
CreateGroup(rw http.ResponseWriter, r *http.Request)
UpdateGroup(rw http.ResponseWriter, r *http.Request)
DeleteGroup(rw http.ResponseWriter, r *http.Request)
// Mapper handlers
ListMappers(rw http.ResponseWriter, r *http.Request)
CreateMapper(rw http.ResponseWriter, r *http.Request)
UpdateMapper(rw http.ResponseWriter, r *http.Request)
DeleteMapper(rw http.ResponseWriter, r *http.Request)
}

View File

@@ -204,7 +204,7 @@ func AdjustKey(key *telemetrytypes.TelemetryFieldKey, keys map[string][]*telemet
// Downstream query builder should handle multiple matching keys with their own metadata
// and not rely on this function to do so.
materialized := true
indexes := []telemetrytypes.TelemetryFieldKeySkipIndex{}
indexes := []telemetrytypes.JSONDataTypeIndex{}
fieldContextsSeen := map[telemetrytypes.FieldContext]bool{}
dataTypesSeen := map[telemetrytypes.FieldDataType]bool{}
for _, matchingKey := range matchingKeys {

View File

@@ -36,8 +36,6 @@ import (
"github.com/SigNoz/signoz/pkg/modules/serviceaccount/implserviceaccount"
"github.com/SigNoz/signoz/pkg/modules/services"
"github.com/SigNoz/signoz/pkg/modules/services/implservices"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/modules/spanmapper/implspanmapper"
"github.com/SigNoz/signoz/pkg/modules/spanpercentile"
"github.com/SigNoz/signoz/pkg/modules/spanpercentile/implspanpercentile"
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
@@ -73,7 +71,6 @@ type Handlers struct {
RegistryHandler factory.Handler
CloudIntegrationHandler cloudintegration.Handler
RuleStateHistory rulestatehistory.Handler
SpanMapperHandler spanmapper.Handler
AlertmanagerHandler alertmanager.Handler
TraceDetail tracedetail.Handler
RulerHandler ruler.Handler
@@ -117,7 +114,6 @@ func NewHandlers(
RegistryHandler: registryHandler,
RuleStateHistory: implrulestatehistory.NewHandler(modules.RuleStateHistory),
CloudIntegrationHandler: implcloudintegration.NewHandler(modules.CloudIntegration),
SpanMapperHandler: implspanmapper.NewHandler(nil, providerSettings), // todo(nitya): will update this in future PR
AlertmanagerHandler: signozalertmanager.NewHandler(alertmanagerService),
TraceDetail: impltracedetail.NewHandler(modules.TraceDetail),
RulerHandler: signozruler.NewHandler(rulerService),

View File

@@ -21,8 +21,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/fields"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/preference"
"github.com/SigNoz/signoz/pkg/modules/promote"
@@ -30,7 +30,6 @@ import (
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
@@ -75,7 +74,6 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
struct{ factory.Handler }{},
struct{ cloudintegration.Handler }{},
struct{ rulestatehistory.Handler }{},
struct{ spanmapper.Handler }{},
struct{ alertmanager.Handler }{},
struct{ tracedetail.Handler }{},
struct{ ruler.Handler }{},

View File

@@ -281,7 +281,6 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
handlers.RegistryHandler,
handlers.CloudIntegrationHandler,
handlers.RuleStateHistory,
handlers.SpanMapperHandler,
handlers.AlertmanagerHandler,
handlers.TraceDetail,
handlers.RulerHandler,

View File

@@ -195,8 +195,8 @@ func (c *jsonConditionBuilder) buildPrimitiveTerminalCondition(node *telemetryty
// the field genuinely holds the empty/zero value.
//
// Note: indexing is also skipped for Array Nested fields because they cannot be indexed.
indexed := slices.ContainsFunc(node.TerminalConfig.Key.Indexes, func(index telemetrytypes.TelemetryFieldKeySkipIndex) bool {
return telemetrytypes.MappingFieldDataTypeToJSONDataType[index.FieldDataType] == node.TerminalConfig.ElemType
indexed := slices.ContainsFunc(node.TerminalConfig.Key.Indexes, func(index telemetrytypes.JSONDataTypeIndex) bool {
return index.Type == node.TerminalConfig.ElemType
})
isExistsCheck := operator == qbtypes.FilterOperatorExists || operator == qbtypes.FilterOperatorNotExists
if node.TerminalConfig.ElemType.IndexSupported && indexed && !isExistsCheck {

View File

@@ -1127,12 +1127,9 @@ func buildTestTelemetryMetadataStore(t *testing.T, addIndexes bool) *telemetryty
return entry.Path == path && entry.Type == jsonType
})
if idx >= 0 {
key.Indexes = append(key.Indexes, telemetrytypes.TelemetryFieldKeySkipIndex{
Name: path,
FieldContext: telemetrytypes.FieldContextBody,
FieldDataType: fdt,
BaseColumn: LogsV2BodyV2Column,
IndexExpression: schemamigrator.JSONSubColumnIndexExpr(LogsV2BodyV2Column, path, jsonType.StringValue()),
key.Indexes = append(key.Indexes, telemetrytypes.JSONDataTypeIndex{
Type: jsonType,
ColumnExpression: schemamigrator.JSONSubColumnIndexExpr(LogsV2BodyV2Column, path, jsonType.StringValue()),
})
}
}

View File

@@ -106,7 +106,7 @@ func (t *telemetryMetaStore) buildJSONPlans(keys []*telemetrytypes.TelemetryFiel
return nil
}
func (t *telemetryMetaStore) getJSONPathIndexes(ctx context.Context, paths ...string) (map[string][]telemetrytypes.TelemetryFieldKeySkipIndex, error) {
func (t *telemetryMetaStore) getJSONPathIndexes(ctx context.Context, paths ...string) (map[string][]telemetrytypes.JSONDataTypeIndex, error) {
filteredPaths := []string{}
for _, path := range paths {
// skip array paths; since they don't have any indexes
@@ -116,22 +116,47 @@ func (t *telemetryMetaStore) getJSONPathIndexes(ctx context.Context, paths ...st
filteredPaths = append(filteredPaths, path)
}
if len(filteredPaths) == 0 {
return make(map[string][]telemetrytypes.TelemetryFieldKeySkipIndex), nil
return make(map[string][]telemetrytypes.JSONDataTypeIndex), nil
}
// list indexes for the paths
indexes, err := t.ListLogsJSONIndexes(ctx, filteredPaths...)
indexesMap, err := t.ListLogsJSONIndexes(ctx, filteredPaths...)
if err != nil {
return nil, errors.WrapInternalf(err, CodeFailLoadLogsJSONIndexes, "failed to list JSON path indexes")
}
// build a set of indexes
fieldPathToIndexes := make(map[string][]telemetrytypes.TelemetryFieldKeySkipIndex)
for _, index := range indexes {
fieldPathToIndexes[index.Name] = append(fieldPathToIndexes[index.Name], index)
cleanIndexes := make(map[string][]telemetrytypes.JSONDataTypeIndex)
for path, indexes := range indexesMap {
for _, index := range indexes {
columnExpr, columnType, err := schemamigrator.UnfoldJSONSubColumnIndexExpr(index.Expression)
if err != nil {
return nil, errors.WrapInternalf(err, CodeFailLoadLogsJSONIndexes, "failed to unfold JSON sub column index expression: %s", index.Expression)
}
jsonDataType, found := telemetrytypes.MappingStringToJSONDataType[columnType]
if !found {
t.logger.ErrorContext(ctx, "failed to map column type to JSON data type", slog.String("column_type", columnType), slog.String("column_expr", columnExpr))
continue
}
if jsonDataType == telemetrytypes.String {
cleanIndexes[path] = append(cleanIndexes[path], telemetrytypes.JSONDataTypeIndex{
Type: telemetrytypes.String,
ColumnExpression: columnExpr,
IndexExpression: index.Expression,
})
} else if strings.HasPrefix(index.Type, "minmax") {
cleanIndexes[path] = append(cleanIndexes[path], telemetrytypes.JSONDataTypeIndex{
Type: jsonDataType,
ColumnExpression: columnExpr,
IndexExpression: index.Expression,
})
}
}
}
return fieldPathToIndexes, nil
return cleanIndexes, nil
}
func buildListLogsJSONIndexesQuery(cluster string, filters ...string) (string, []any) {
@@ -148,15 +173,14 @@ func buildListLogsJSONIndexesQuery(cluster string, filters ...string) (string, [
filterExprs := []string{}
for _, filter := range filters {
// Remove backticks from actual expr cuz paths from metadata doesn't have backticks
filterExprs = append(filterExprs, sb.ILike("replaceAll(expr, '`', '')", fmt.Sprintf("%%%s%%", querybuilder.FormatValueForContains(filter))))
filterExprs = append(filterExprs, sb.ILike("expr", fmt.Sprintf("%%%s%%", querybuilder.FormatValueForContains(filter))))
}
sb.Where(sb.Or(filterExprs...))
return sb.BuildWithFlavor(sqlbuilder.ClickHouse)
}
func (t *telemetryMetaStore) ListLogsJSONIndexes(ctx context.Context, filters ...string) ([]telemetrytypes.TelemetryFieldKeySkipIndex, error) {
func (t *telemetryMetaStore) ListLogsJSONIndexes(ctx context.Context, filters ...string) (map[string][]schemamigrator.Index, error) {
ctx = withTelemetryContext(ctx, "ListLogsJSONIndexes")
query, args := buildListLogsJSONIndexesQuery(t.telemetrystore.Cluster(), filters...)
rows, err := t.telemetrystore.ClickhouseDB().Query(ctx, query, args...)
@@ -165,7 +189,7 @@ func (t *telemetryMetaStore) ListLogsJSONIndexes(ctx context.Context, filters ..
}
defer rows.Close()
indexes := []telemetrytypes.TelemetryFieldKeySkipIndex{}
indexes := make(map[string][]schemamigrator.Index)
for rows.Next() {
var name string
var typeFull string
@@ -174,39 +198,11 @@ func (t *telemetryMetaStore) ListLogsJSONIndexes(ctx context.Context, filters ..
if err := rows.Scan(&name, &typeFull, &expr, &granularity); err != nil {
return nil, errors.WrapInternalf(err, CodeFailLoadLogsJSONIndexes, "failed to scan string indexed column")
}
columnExpr, columnType, err := schemamigrator.UnfoldJSONSubColumnIndexExpr(expr)
if err != nil {
return nil, errors.WrapInternalf(err, CodeFailLoadLogsJSONIndexes, "failed to unfold JSON sub column index expression: %s", expr)
}
fdt, found := telemetrytypes.MappingJSONDataTypeToFieldDataType[columnType]
if !found {
t.logger.ErrorContext(ctx, "failed to map JSON data type to field data type", slog.String("column_type", columnType), slog.String("column_expr", columnExpr))
continue
}
baseColumn := ""
fieldName := ""
switch {
case strings.HasPrefix(columnExpr, telemetrylogs.BodyV2ColumnPrefix):
baseColumn = telemetrylogs.BodyV2ColumnPrefix
fieldName = strings.TrimPrefix(columnExpr, telemetrylogs.BodyV2ColumnPrefix)
case strings.HasPrefix(columnExpr, telemetrylogs.BodyPromotedColumnPrefix):
baseColumn = telemetrylogs.BodyPromotedColumnPrefix
fieldName = strings.TrimPrefix(columnExpr, telemetrylogs.BodyPromotedColumnPrefix)
}
fieldName = strings.ReplaceAll(fieldName, "`", "")
indexes = append(indexes, telemetrytypes.TelemetryFieldKeySkipIndex{
Name: fieldName,
FieldContext: telemetrytypes.FieldContextBody,
FieldDataType: fdt,
BaseColumn: baseColumn,
IndexName: name,
IndexType: typeFull,
IndexExpression: expr,
Granularity: int(granularity),
indexes[name] = append(indexes[name], schemamigrator.Index{
Name: name,
Type: typeFull,
Expression: expr,
Granularity: int(granularity),
})
}

View File

@@ -36,7 +36,7 @@ func TestBuildListLogsJSONIndexesQuery(t *testing.T) {
cluster: "test-cluster",
filters: []string{"foo", "bar"},
expectedSQL: "SELECT name, type_full, expr, granularity FROM clusterAllReplicas('test-cluster', system.data_skipping_indices) " +
"WHERE database = ? AND table = ? AND (LOWER(expr) LIKE LOWER(?) OR LOWER(expr) LIKE LOWER(?)) AND (LOWER(replaceAll(expr, '`', '')) LIKE LOWER(?) OR LOWER(replaceAll(expr, '`', '')) LIKE LOWER(?))",
"WHERE database = ? AND table = ? AND (LOWER(expr) LIKE LOWER(?) OR LOWER(expr) LIKE LOWER(?)) AND (LOWER(expr) LIKE LOWER(?) OR LOWER(expr) LIKE LOWER(?))",
expectedArgs: []any{
telemetrylogs.DBName,
telemetrylogs.LogsV2LocalTableName,

View File

@@ -1,39 +1,5 @@
package cloudintegrationtypes
import (
"bytes"
"fmt"
"text/template"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
AgentArmTemplateStorePath = "https://signoz-integrations.s3.us-east-1.amazonaws.com/azure-arm-template-%s.json"
AgentDeploymentStackName = "signoz-integration"
// Default values for fixed ARM template parameters.
armDefaultRgName = "signoz-integration-rg"
armDefaultContainerEnvName = "signoz-integration-agent-env"
armDefaultDeploymentEnv = "production"
// ARM template parameter key names used in both CLI and PowerShell deployment commands.
armParamLocation = "location"
armParamSignozAPIKey = "signozApiKey"
armParamSignozAPIUrl = "signozApiUrl"
armParamSignozIngestionURL = "signozIngestionUrl"
armParamSignozIngestionKey = "signozIngestionKey"
armParamAccountID = "signozIntegrationAccountId"
armParamAgentVersion = "signozIntegrationAgentVersion"
armParamRgName = "rgName"
armParamContainerEnvName = "containerEnvName"
armParamDeploymentEnv = "deploymentEnv"
// command templates.
azureCLITemplate = template.Must(template.New("azureCLI").Parse(azureCLITemplateStr()))
azurePowerShellTemplate = template.Must(template.New("azurePS").Parse(azurePowerShellTemplateStr()))
)
type AzureAccountConfig struct {
DeploymentRegion string `json:"deploymentRegion" required:"true"`
ResourceGroups []string `json:"resourceGroups" required:"true" nullable:"false"`
@@ -85,36 +51,6 @@ type AzureIntegrationConfig struct {
TelemetryCollectionStrategy []*AzureTelemetryCollectionStrategy `json:"telemetryCollectionStrategy" required:"true" nullable:"false"`
}
// azureTemplateData is the data struct passed to both command templates.
// All fields are exported so text/template can access them.
type azureTemplateData struct {
// Deploy parameter values.
TemplateURL string
Location string
SignozAPIKey string
SignozAPIUrl string
SignozIngestionURL string
SignozIngestionKey string
AccountID string
AgentVersion string
// ARM parameter key names (from package-level vars).
StackName string
ParamLocation string
ParamSignozAPIKey string
ParamSignozAPIUrl string
ParamSignozIngestionURL string
ParamSignozIngestionKey string
ParamAccountID string
ParamAgentVersion string
ParamRgName string
ParamContainerEnvName string
ParamDeploymentEnv string
// Fixed default values.
DefaultRgName string
DefaultContainerEnvName string
DefaultDeploymentEnv string
}
func NewAzureIntegrationConfig(
deploymentRegion string,
resourceGroups []string,
@@ -126,105 +62,3 @@ func NewAzureIntegrationConfig(
TelemetryCollectionStrategy: strategies,
}
}
func NewAzureConnectionArtifact(
accountID valuer.UUID,
agentVersion string,
creds *Credentials,
cfg *AzurePostableAccountConfig,
) (*AzureConnectionArtifact, error) {
data := azureTemplateData{
TemplateURL: fmt.Sprintf(AgentArmTemplateStorePath, agentVersion),
Location: cfg.DeploymentRegion,
SignozAPIKey: creds.SigNozAPIKey,
SignozAPIUrl: creds.SigNozAPIURL,
SignozIngestionURL: creds.IngestionURL,
SignozIngestionKey: creds.IngestionKey,
AccountID: accountID.StringValue(),
AgentVersion: agentVersion,
StackName: AgentDeploymentStackName,
ParamLocation: armParamLocation,
ParamSignozAPIKey: armParamSignozAPIKey,
ParamSignozAPIUrl: armParamSignozAPIUrl,
ParamSignozIngestionURL: armParamSignozIngestionURL,
ParamSignozIngestionKey: armParamSignozIngestionKey,
ParamAccountID: armParamAccountID,
ParamAgentVersion: armParamAgentVersion,
ParamRgName: armParamRgName,
ParamContainerEnvName: armParamContainerEnvName,
ParamDeploymentEnv: armParamDeploymentEnv,
DefaultRgName: armDefaultRgName,
DefaultContainerEnvName: armDefaultContainerEnvName,
DefaultDeploymentEnv: armDefaultDeploymentEnv,
}
cliCommand, err := newAzureConnectionCLICommand(data)
if err != nil {
return nil, err
}
psCommand, err := newAzureConnectionPowerShellCommand(data)
if err != nil {
return nil, err
}
return &AzureConnectionArtifact{
CLICommand: cliCommand,
CloudPowerShellCommand: psCommand,
}, nil
}
func newAzureConnectionCLICommand(data azureTemplateData) (string, error) {
var buf bytes.Buffer
err := azureCLITemplate.Execute(&buf, data)
if err != nil {
return "", err
}
return buf.String(), nil
}
func newAzureConnectionPowerShellCommand(data azureTemplateData) (string, error) {
var buf bytes.Buffer
err := azurePowerShellTemplate.Execute(&buf, data)
if err != nil {
return "", err
}
return buf.String(), nil
}
func azureCLITemplateStr() string {
return `az stack sub create \
--name {{.StackName}} \
--location {{.Location}} \
--template-uri {{.TemplateURL}} \
--parameters \
{{.ParamLocation}}='{{.Location}}' \
{{.ParamSignozAPIKey}}='{{.SignozAPIKey}}' \
{{.ParamSignozAPIUrl}}='{{.SignozAPIUrl}}' \
{{.ParamSignozIngestionURL}}='{{.SignozIngestionURL}}' \
{{.ParamSignozIngestionKey}}='{{.SignozIngestionKey}}' \
{{.ParamAccountID}}='{{.AccountID}}' \
{{.ParamAgentVersion}}='{{.AgentVersion}}' \
--action-on-unmanage deleteAll \
--deny-settings-mode denyDelete`
}
func azurePowerShellTemplateStr() string {
return "New-AzSubscriptionDeploymentStack `\n" +
" -Name \"{{.StackName}}\" `\n" +
" -Location \"{{.Location}}\" `\n" +
" -TemplateUri \"{{.TemplateURL}}\" `\n" +
" -TemplateParameterObject @{\n" +
" {{.ParamLocation}} = \"{{.Location}}\"\n" +
" {{.ParamSignozAPIKey}} = \"{{.SignozAPIKey}}\"\n" +
" {{.ParamSignozAPIUrl}} = \"{{.SignozAPIUrl}}\"\n" +
" {{.ParamSignozIngestionURL}} = \"{{.SignozIngestionURL}}\"\n" +
" {{.ParamSignozIngestionKey}} = \"{{.SignozIngestionKey}}\"\n" +
" {{.ParamAccountID}} = \"{{.AccountID}}\"\n" +
" {{.ParamAgentVersion}} = \"{{.AgentVersion}}\"\n" +
" {{.ParamRgName}} = \"{{.DefaultRgName}}\"\n" +
" {{.ParamContainerEnvName}} = \"{{.DefaultContainerEnvName}}\"\n" +
" {{.ParamDeploymentEnv}} = \"{{.DefaultDeploymentEnv}}\"\n" +
" } `\n" +
" -ActionOnUnmanage \"deleteAll\" `\n" +
" -DenySettingsMode \"denyDelete\""
}

View File

@@ -1,142 +0,0 @@
// file is generated by AI
package cloudintegrationtypes
import (
"fmt"
"strings"
"testing"
"github.com/SigNoz/signoz/pkg/valuer"
)
var testTemplateData = azureTemplateData{
TemplateURL: fmt.Sprintf(AgentArmTemplateStorePath, "v0.1.0"),
Location: "eastus",
SignozAPIKey: "test-api-key",
SignozAPIUrl: "https://signoz.example.com",
SignozIngestionURL: "https://ingest.example.com",
SignozIngestionKey: "test-ingest-key",
AccountID: "acct-123",
AgentVersion: "v0.1.0",
StackName: AgentDeploymentStackName,
ParamLocation: armParamLocation,
ParamSignozAPIKey: armParamSignozAPIKey,
ParamSignozAPIUrl: armParamSignozAPIUrl,
ParamSignozIngestionURL: armParamSignozIngestionURL,
ParamSignozIngestionKey: armParamSignozIngestionKey,
ParamAccountID: armParamAccountID,
ParamAgentVersion: armParamAgentVersion,
ParamRgName: armParamRgName,
ParamContainerEnvName: armParamContainerEnvName,
ParamDeploymentEnv: armParamDeploymentEnv,
DefaultRgName: armDefaultRgName,
DefaultContainerEnvName: armDefaultContainerEnvName,
DefaultDeploymentEnv: armDefaultDeploymentEnv,
}
func TestNewAzureConnectionCLICommand(t *testing.T) {
cmd, err := newAzureConnectionCLICommand(testTemplateData)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
mustContain := []string{
"az stack sub create",
fmt.Sprintf("--name %s", AgentDeploymentStackName),
fmt.Sprintf("--location %s", testTemplateData.Location),
fmt.Sprintf("--template-uri %s", testTemplateData.TemplateURL),
fmt.Sprintf("%s='%s'", armParamLocation, testTemplateData.Location),
fmt.Sprintf("%s='%s'", armParamSignozAPIKey, testTemplateData.SignozAPIKey),
fmt.Sprintf("%s='%s'", armParamSignozAPIUrl, testTemplateData.SignozAPIUrl),
fmt.Sprintf("%s='%s'", armParamSignozIngestionURL, testTemplateData.SignozIngestionURL),
fmt.Sprintf("%s='%s'", armParamSignozIngestionKey, testTemplateData.SignozIngestionKey),
fmt.Sprintf("%s='%s'", armParamAccountID, testTemplateData.AccountID),
fmt.Sprintf("%s='%s'", armParamAgentVersion, testTemplateData.AgentVersion),
"--action-on-unmanage deleteAll",
"--deny-settings-mode denyDelete",
}
for _, fragment := range mustContain {
if !strings.Contains(cmd, fragment) {
t.Errorf("CLI command missing %q\ngot:\n%s", fragment, cmd)
}
}
// Lines must be joined with the bash line-continuation separator.
if !strings.Contains(cmd, " \\\n") {
t.Errorf("CLI command missing line-continuation separator ' \\\\\\n'\ngot:\n%s", cmd)
}
}
func TestNewAzureConnectionPowerShellCommand(t *testing.T) {
cmd, err := newAzureConnectionPowerShellCommand(testTemplateData)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
mustContain := []string{
"New-AzSubscriptionDeploymentStack",
fmt.Sprintf("-Name \"%s\"", AgentDeploymentStackName),
fmt.Sprintf("-Location \"%s\"", testTemplateData.Location),
fmt.Sprintf("-TemplateUri \"%s\"", testTemplateData.TemplateURL),
armParamLocation,
armParamSignozAPIKey,
armParamSignozAPIUrl,
armParamSignozIngestionURL,
armParamSignozIngestionKey,
armParamAccountID,
armParamAgentVersion,
armParamRgName,
armParamContainerEnvName,
armParamDeploymentEnv,
armDefaultRgName,
armDefaultContainerEnvName,
armDefaultDeploymentEnv,
"-ActionOnUnmanage \"deleteAll\"",
"-DenySettingsMode \"denyDelete\"",
}
for _, fragment := range mustContain {
if !strings.Contains(cmd, fragment) {
t.Errorf("PowerShell command missing %q\ngot:\n%s", fragment, cmd)
}
}
// Final command must not end with a line-continuation backtick.
if strings.HasSuffix(strings.TrimSpace(cmd), "`") {
t.Errorf("PowerShell command must not end with a backtick\ngot:\n%s", cmd)
}
}
func TestNewAzureConnectionArtifact(t *testing.T) {
accountID := valuer.GenerateUUID()
agentVersion := "v0.1.0"
creds := &Credentials{
SigNozAPIURL: "https://signoz.example.com",
SigNozAPIKey: "test-api-key",
IngestionURL: "https://ingest.example.com",
IngestionKey: "test-ingest-key",
}
cfg := &AzurePostableAccountConfig{
DeploymentRegion: "eastus",
ResourceGroups: []string{"rg1"},
}
artifact, err := NewAzureConnectionArtifact(accountID, agentVersion, creds, cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if artifact.CLICommand == "" {
t.Error("CLICommand must not be empty")
}
if artifact.CloudPowerShellCommand == "" {
t.Error("CloudPowerShellCommand must not be empty")
}
if !strings.Contains(artifact.CLICommand, accountID.StringValue()) {
t.Errorf("CLICommand must contain accountID %q", accountID.StringValue())
}
if !strings.Contains(artifact.CloudPowerShellCommand, accountID.StringValue()) {
t.Errorf("CloudPowerShellCommand must contain accountID %q", accountID.StringValue())
}
}

View File

@@ -239,10 +239,6 @@ func (service *CloudIntegrationService) Update(provider CloudProviderType, servi
}
// other validations happen in newStorableServiceConfig
case CloudProviderTypeAzure:
if config.Azure == nil {
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "Azure config is required for Azure service")
}
default:
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}

View File

@@ -49,7 +49,7 @@ type HostFilter struct {
FilterByStatus HostStatus `json:"filterByStatus"`
}
// Validate ensures HostsListRequest contains acceptable values.
// Validate ensures PostableHosts contains acceptable values.
func (req *PostableHosts) Validate() error {
if req == nil {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "request is nil")

View File

@@ -0,0 +1,83 @@
package inframonitoringtypes
import (
"slices"
"github.com/SigNoz/signoz/pkg/errors"
)
// PostableOnboarding is the request for GET /api/v2/infra_monitoring/onboarding.
// The single `type` query param selects which infra-monitoring subsection the
// readiness check runs for.
type PostableOnboarding struct {
Type OnboardingType `query:"type" required:"true"`
}
// Validate rejects empty/unknown onboarding types.
func (req *PostableOnboarding) Validate() error {
if req == nil {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "request is nil")
}
if req.Type.IsZero() {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "type is required")
}
if !slices.Contains(ValidOnboardingTypes, req.Type) {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid type: %s", req.Type)
}
return nil
}
// Onboarding is the response for GET /api/v2/infra_monitoring/onboarding.
//
// The three present/missing pairs partition a type's requirements into three
// dimensions — default-enabled metrics, optional metrics, required attributes —
// each bucketed by the collector component (receiver or processor) that
// produces it. Ready is true iff every Missing* array is empty.
type Onboarding struct {
Type OnboardingType `json:"type" required:"true"`
Ready bool `json:"ready" required:"true"`
PresentDefaultEnabledMetrics []MetricsComponentEntry `json:"presentDefaultEnabledMetrics" required:"true"`
PresentOptionalMetrics []MetricsComponentEntry `json:"presentOptionalMetrics" required:"true"`
PresentRequiredAttributes []AttributesComponentEntry `json:"presentRequiredAttributes" required:"true"`
MissingDefaultEnabledMetrics []MissingMetricsComponentEntry `json:"missingDefaultEnabledMetrics" required:"true"`
MissingOptionalMetrics []MissingMetricsComponentEntry `json:"missingOptionalMetrics" required:"true"`
MissingRequiredAttributes []MissingAttributesComponentEntry `json:"missingRequiredAttributes" required:"true"`
}
// AssociatedComponent identifies the collector receiver or processor that a
// metric or attribute originates from. Name is free-form (e.g. "kubeletstatsreceiver").
type AssociatedComponent struct {
Type OnboardingComponentType `json:"type" required:"true"`
Name string `json:"name" required:"true"`
}
// MetricsComponentEntry lists metrics that share a single associated component.
type MetricsComponentEntry struct {
Metrics []string `json:"metrics" required:"true"`
AssociatedComponent AssociatedComponent `json:"associatedComponent" required:"true"`
}
// AttributesComponentEntry lists resource attributes that share a single associated component.
type AttributesComponentEntry struct {
Attributes []string `json:"attributes" required:"true"`
AssociatedComponent AssociatedComponent `json:"associatedComponent" required:"true"`
}
// MissingMetricsComponentEntry extends MetricsComponentEntry with a user-facing
// message and a docs link for fixing the missing metrics.
type MissingMetricsComponentEntry struct {
MetricsComponentEntry
Message string `json:"message" required:"true"`
DocumentationLink string `json:"documentationLink" required:"true"`
}
// MissingAttributesComponentEntry extends AttributesComponentEntry with a user-facing
// message and a docs link for fixing the missing attributes.
type MissingAttributesComponentEntry struct {
AttributesComponentEntry
Message string `json:"message" required:"true"`
DocumentationLink string `json:"documentationLink" required:"true"`
}

View File

@@ -0,0 +1,71 @@
package inframonitoringtypes
import "github.com/SigNoz/signoz/pkg/valuer"
// OnboardingType identifies a single infra-monitoring subsection (UI tab).
// One value per v1/v2 list API we surface in the infra-monitoring section.
type OnboardingType struct {
valuer.String
}
var (
OnboardingTypeHosts = OnboardingType{valuer.NewString("hosts")}
OnboardingTypeProcesses = OnboardingType{valuer.NewString("processes")}
OnboardingTypePods = OnboardingType{valuer.NewString("pods")}
OnboardingTypeNodes = OnboardingType{valuer.NewString("nodes")}
OnboardingTypeDeployments = OnboardingType{valuer.NewString("deployments")}
OnboardingTypeDaemonsets = OnboardingType{valuer.NewString("daemonsets")}
OnboardingTypeStatefulsets = OnboardingType{valuer.NewString("statefulsets")}
OnboardingTypeJobs = OnboardingType{valuer.NewString("jobs")}
OnboardingTypeNamespaces = OnboardingType{valuer.NewString("namespaces")}
OnboardingTypeClusters = OnboardingType{valuer.NewString("clusters")}
OnboardingTypeVolumes = OnboardingType{valuer.NewString("volumes")}
)
func (OnboardingType) Enum() []any {
return []any{
OnboardingTypeHosts,
OnboardingTypeProcesses,
OnboardingTypePods,
OnboardingTypeNodes,
OnboardingTypeDeployments,
OnboardingTypeDaemonsets,
OnboardingTypeStatefulsets,
OnboardingTypeJobs,
OnboardingTypeNamespaces,
OnboardingTypeClusters,
OnboardingTypeVolumes,
}
}
var ValidOnboardingTypes = []OnboardingType{
OnboardingTypeHosts,
OnboardingTypeProcesses,
OnboardingTypePods,
OnboardingTypeNodes,
OnboardingTypeDeployments,
OnboardingTypeDaemonsets,
OnboardingTypeStatefulsets,
OnboardingTypeJobs,
OnboardingTypeNamespaces,
OnboardingTypeClusters,
OnboardingTypeVolumes,
}
// OnboardingComponentType tags each AssociatedComponent as either a receiver or a processor.
// Only these two values are ever written by the module.
type OnboardingComponentType struct {
valuer.String
}
var (
OnboardingComponentTypeReceiver = OnboardingComponentType{valuer.NewString("receiver")}
OnboardingComponentTypeProcessor = OnboardingComponentType{valuer.NewString("processor")}
)
func (OnboardingComponentType) Enum() []any {
return []any{
OnboardingComponentTypeReceiver,
OnboardingComponentTypeProcessor,
}
}

View File

@@ -0,0 +1,110 @@
package inframonitoringtypes
import (
"testing"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestPostableOnboarding_Validate(t *testing.T) {
tests := []struct {
name string
req *PostableOnboarding
wantErr bool
}{
{
name: "nil request",
req: nil,
wantErr: true,
},
{
name: "empty type",
req: &PostableOnboarding{},
wantErr: true,
},
{
name: "unknown type",
req: &PostableOnboarding{Type: OnboardingType{valuer.NewString("foo")}},
wantErr: true,
},
{
name: "hosts",
req: &PostableOnboarding{Type: OnboardingTypeHosts},
wantErr: false,
},
{
name: "processes",
req: &PostableOnboarding{Type: OnboardingTypeProcesses},
wantErr: false,
},
{
name: "pods",
req: &PostableOnboarding{Type: OnboardingTypePods},
wantErr: false,
},
{
name: "nodes",
req: &PostableOnboarding{Type: OnboardingTypeNodes},
wantErr: false,
},
{
name: "deployments",
req: &PostableOnboarding{Type: OnboardingTypeDeployments},
wantErr: false,
},
{
name: "daemonsets",
req: &PostableOnboarding{Type: OnboardingTypeDaemonsets},
wantErr: false,
},
{
name: "statefulsets",
req: &PostableOnboarding{Type: OnboardingTypeStatefulsets},
wantErr: false,
},
{
name: "jobs",
req: &PostableOnboarding{Type: OnboardingTypeJobs},
wantErr: false,
},
{
name: "namespaces",
req: &PostableOnboarding{Type: OnboardingTypeNamespaces},
wantErr: false,
},
{
name: "clusters",
req: &PostableOnboarding{Type: OnboardingTypeClusters},
wantErr: false,
},
{
name: "volumes",
req: &PostableOnboarding{Type: OnboardingTypeVolumes},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.req.Validate()
if tt.wantErr {
require.Error(t, err)
require.True(t, errors.Ast(err, errors.TypeInvalidInput), "expected error to be of type InvalidInput")
} else {
require.NoError(t, err)
}
})
}
}
// TestValidOnboardingTypes_MatchesEnum ensures the ValidOnboardingTypes slice
// stays in sync with the Enum() list — both must cover every OnboardingType value.
func TestValidOnboardingTypes_MatchesEnum(t *testing.T) {
enum := OnboardingType{}.Enum()
require.Equal(t, len(enum), len(ValidOnboardingTypes))
for i, v := range enum {
require.Equal(t, v, ValidOnboardingTypes[i])
}
}

View File

@@ -0,0 +1,104 @@
package inframonitoringtypes
import (
"encoding/json"
"slices"
"github.com/SigNoz/signoz/pkg/errors"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
)
type Pods struct {
Type ResponseType `json:"type" required:"true"`
Records []PodRecord `json:"records" required:"true"`
Total int `json:"total" required:"true"`
RequiredMetricsCheck RequiredMetricsCheck `json:"requiredMetricsCheck" required:"true"`
EndTimeBeforeRetention bool `json:"endTimeBeforeRetention" required:"true"`
Warning *qbtypes.QueryWarnData `json:"warning,omitempty"`
}
type PodRecord struct {
PodUID string `json:"podUID" required:"true"`
PodCPU float64 `json:"podCPU" required:"true"`
PodCPURequest float64 `json:"podCPURequest" required:"true"`
PodCPULimit float64 `json:"podCPULimit" required:"true"`
PodMemory float64 `json:"podMemory" required:"true"`
PodMemoryRequest float64 `json:"podMemoryRequest" required:"true"`
PodMemoryLimit float64 `json:"podMemoryLimit" required:"true"`
PodPhase PodPhase `json:"podPhase" required:"true"`
PodAge int64 `json:"podAge" required:"true"`
Meta map[string]interface{} `json:"meta" required:"true"`
}
// PostablePods is the request body for the v2 pods list API.
type PostablePods struct {
Start int64 `json:"start" required:"true"`
End int64 `json:"end" required:"true"`
Filter *qbtypes.Filter `json:"filter"`
GroupBy []qbtypes.GroupByKey `json:"groupBy"`
OrderBy *qbtypes.OrderBy `json:"orderBy"`
Offset int `json:"offset"`
Limit int `json:"limit" required:"true"`
}
// Validate ensures PostablePods contains acceptable values.
func (req *PostablePods) Validate() error {
if req == nil {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "request is nil")
}
if req.Start <= 0 {
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"invalid start time %d: start must be greater than 0",
req.Start,
)
}
if req.End <= 0 {
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"invalid end time %d: end must be greater than 0",
req.End,
)
}
if req.Start >= req.End {
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"invalid time range: start (%d) must be less than end (%d)",
req.Start,
req.End,
)
}
if req.Limit < 1 || req.Limit > 5000 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "limit must be between 1 and 5000")
}
if req.Offset < 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "offset cannot be negative")
}
if req.OrderBy != nil {
if !slices.Contains(PodsValidOrderByKeys, req.OrderBy.Key.Name) {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid order by key: %s", req.OrderBy.Key.Name)
}
if req.OrderBy.Direction != qbtypes.OrderDirectionAsc && req.OrderBy.Direction != qbtypes.OrderDirectionDesc {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid order by direction: %s", req.OrderBy.Direction)
}
}
return nil
}
// UnmarshalJSON validates input immediately after decoding.
func (req *PostablePods) UnmarshalJSON(data []byte) error {
type raw PostablePods
var decoded raw
if err := json.Unmarshal(data, &decoded); err != nil {
return err
}
*req = PostablePods(decoded)
return req.Validate()
}

View File

@@ -0,0 +1,45 @@
package inframonitoringtypes
import "github.com/SigNoz/signoz/pkg/valuer"
type PodPhase struct {
valuer.String
}
var (
PodPhasePending = PodPhase{valuer.NewString("pending")}
PodPhaseRunning = PodPhase{valuer.NewString("running")}
PodPhaseSucceeded = PodPhase{valuer.NewString("succeeded")}
PodPhaseFailed = PodPhase{valuer.NewString("failed")}
PodPhaseUnknown = PodPhase{valuer.NewString("unknown")}
PodPhaseNone = PodPhase{valuer.NewString("")}
)
func (PodPhase) Enum() []any {
return []any{
PodPhasePending,
PodPhaseRunning,
PodPhaseSucceeded,
PodPhaseFailed,
PodPhaseUnknown,
PodPhaseNone,
}
}
const (
PodsOrderByCPU = "cpu"
PodsOrderByCPURequest = "cpu_request"
PodsOrderByCPULimit = "cpu_limit"
PodsOrderByMemory = "memory"
PodsOrderByMemoryRequest = "memory_request"
PodsOrderByMemoryLimit = "memory_limit"
)
var PodsValidOrderByKeys = []string{
PodsOrderByCPU,
PodsOrderByCPURequest,
PodsOrderByCPULimit,
PodsOrderByMemory,
PodsOrderByMemoryRequest,
PodsOrderByMemoryLimit,
}

View File

@@ -0,0 +1,219 @@
package inframonitoringtypes
import (
"testing"
"github.com/SigNoz/signoz/pkg/errors"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestPostablePods_Validate(t *testing.T) {
tests := []struct {
name string
req *PostablePods
wantErr bool
}{
{
name: "valid request",
req: &PostablePods{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
},
wantErr: false,
},
{
name: "nil request",
req: nil,
wantErr: true,
},
{
name: "start time zero",
req: &PostablePods{
Start: 0,
End: 2000,
Limit: 100,
Offset: 0,
},
wantErr: true,
},
{
name: "start time negative",
req: &PostablePods{
Start: -1000,
End: 2000,
Limit: 100,
Offset: 0,
},
wantErr: true,
},
{
name: "end time zero",
req: &PostablePods{
Start: 1000,
End: 0,
Limit: 100,
Offset: 0,
},
wantErr: true,
},
{
name: "start time greater than end time",
req: &PostablePods{
Start: 2000,
End: 1000,
Limit: 100,
Offset: 0,
},
wantErr: true,
},
{
name: "start time equal to end time",
req: &PostablePods{
Start: 1000,
End: 1000,
Limit: 100,
Offset: 0,
},
wantErr: true,
},
{
name: "limit zero",
req: &PostablePods{
Start: 1000,
End: 2000,
Limit: 0,
Offset: 0,
},
wantErr: true,
},
{
name: "limit negative",
req: &PostablePods{
Start: 1000,
End: 2000,
Limit: -10,
Offset: 0,
},
wantErr: true,
},
{
name: "limit exceeds max",
req: &PostablePods{
Start: 1000,
End: 2000,
Limit: 5001,
Offset: 0,
},
wantErr: true,
},
{
name: "offset negative",
req: &PostablePods{
Start: 1000,
End: 2000,
Limit: 100,
Offset: -5,
},
wantErr: true,
},
{
name: "orderBy nil is valid",
req: &PostablePods{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
},
wantErr: false,
},
{
name: "orderBy with valid key cpu and direction asc",
req: &PostablePods{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
OrderBy: &qbtypes.OrderBy{
Key: qbtypes.OrderByKey{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: PodsOrderByCPU,
},
},
Direction: qbtypes.OrderDirectionAsc,
},
},
wantErr: false,
},
{
name: "orderBy with phase key is invalid",
req: &PostablePods{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
OrderBy: &qbtypes.OrderBy{
Key: qbtypes.OrderByKey{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: "phase",
},
},
Direction: qbtypes.OrderDirectionDesc,
},
},
wantErr: true,
},
{
name: "orderBy with invalid key",
req: &PostablePods{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
OrderBy: &qbtypes.OrderBy{
Key: qbtypes.OrderByKey{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: "unknown",
},
},
Direction: qbtypes.OrderDirectionDesc,
},
},
wantErr: true,
},
{
name: "orderBy with valid key but invalid direction",
req: &PostablePods{
Start: 1000,
End: 2000,
Limit: 100,
Offset: 0,
OrderBy: &qbtypes.OrderBy{
Key: qbtypes.OrderByKey{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: PodsOrderByMemory,
},
},
Direction: qbtypes.OrderDirection{String: valuer.NewString("invalid")},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.req.Validate()
if tt.wantErr {
require.Error(t, err)
require.True(t, errors.Ast(err, errors.TypeInvalidInput), "expected error to be of type InvalidInput")
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -10,10 +10,10 @@ import (
)
type WrappedIndex struct {
JSONDataType telemetrytypes.JSONDataType `json:"-"`
FieldDataType telemetrytypes.FieldDataType `json:"fieldDataType"`
Type string `json:"type"`
Granularity int `json:"granularity"`
JSONDataType telemetrytypes.JSONDataType `json:"-"`
ColumnType string `json:"column_type"`
Type string `json:"type"`
Granularity int `json:"granularity"`
}
type PromotePath struct {
@@ -60,12 +60,12 @@ func (i *PromotePath) ValidateAndSetDefaults() error {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "index granularity must be greater than 0")
}
jsonDataType, ok := telemetrytypes.MappingFieldDataTypeToJSONDataType[index.FieldDataType]
jsonDataType, ok := telemetrytypes.MappingStringToJSONDataType[index.ColumnType]
if !ok {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid column type: %s", index.FieldDataType)
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid column type: %s", index.ColumnType)
}
if !jsonDataType.IndexSupported {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "index is not supported for column type: %s", index.FieldDataType)
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "index is not supported for column type: %s", index.ColumnType)
}
i.Indexes[idx].JSONDataType = jsonDataType

View File

@@ -1,137 +0,0 @@
package spantypes
import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
ErrCodeMapperNotFound = errors.MustNewCode("span_attribute_mapper_not_found")
ErrCodeMapperAlreadyExists = errors.MustNewCode("span_attribute_mapper_already_exists")
ErrCodeMappingInvalidInput = errors.MustNewCode("span_attribute_mapping_invalid_input")
)
// FieldContext is where the target attribute is written.
type FieldContext struct {
valuer.String
}
var (
FieldContextSpanAttribute = FieldContext{valuer.NewString("attribute")}
FieldContextResource = FieldContext{valuer.NewString("resource")}
)
// MapperOperation determines whether the source attribute is moved (deleted) or copied.
type SpanMapperOperation struct {
valuer.String
}
var (
SpanMapperOperationMove = SpanMapperOperation{valuer.NewString("move")}
SpanMapperOperationCopy = SpanMapperOperation{valuer.NewString("copy")}
)
// MapperSource describes one candidate source for a target attribute.
type SpanMapperSource struct {
Key string `json:"key" required:"true"`
Context FieldContext `json:"context" required:"true"`
Operation SpanMapperOperation `json:"operation" required:"true"`
Priority int `json:"priority" required:"true"`
}
// MapperConfig holds the mapping logic for a single target attribute.
// It implements driver.Valuer and sql.Scanner for JSON text column storage.
type SpanMapperConfig struct {
Sources []SpanMapperSource `json:"sources" required:"true" nullable:"true"`
}
// SpanMapper is the domain model for a span attribute mapper.
type SpanMapper struct {
types.TimeAuditable
types.UserAuditable
ID valuer.UUID `json:"id" required:"true"`
GroupID valuer.UUID `json:"group_id" required:"true"`
Name string `json:"name" required:"true"`
FieldContext FieldContext `json:"field_context" required:"true"`
Config SpanMapperConfig `json:"config" required:"true"`
Enabled bool `json:"enabled" required:"true"`
}
type PostableSpanMapper struct {
Name string `json:"name" required:"true"`
FieldContext FieldContext `json:"field_context" required:"true"`
Config SpanMapperConfig `json:"config" required:"true"`
Enabled bool `json:"enabled"`
}
// UpdatableSpanMapper is the HTTP request body for updating a span mapper.
// All fields are optional; only non-nil fields are applied.
type UpdatableSpanMapper struct {
FieldContext FieldContext `json:"field_context,omitempty"`
Config *SpanMapperConfig `json:"config,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
}
type GettableSpanMapper = SpanMapper
type GettableSpanMappers struct {
Items []*GettableSpanMapper `json:"items" required:"true" nullable:"false"`
}
func (FieldContext) Enum() []any {
return []any{FieldContextSpanAttribute, FieldContextResource}
}
func (SpanMapperOperation) Enum() []any {
return []any{SpanMapperOperationMove, SpanMapperOperationCopy}
}
func NewSpanMapperFromStorable(s *StorableSpanMapper) *SpanMapper {
return &SpanMapper{
TimeAuditable: s.TimeAuditable,
UserAuditable: s.UserAuditable,
ID: s.ID,
GroupID: s.GroupID,
Name: s.Name,
FieldContext: s.FieldContext,
Config: s.Config,
Enabled: s.Enabled,
}
}
func NewSpanMapperFromPostable(req *PostableSpanMapper) *SpanMapper {
return &SpanMapper{
Name: req.Name,
FieldContext: req.FieldContext,
Config: req.Config,
Enabled: req.Enabled,
}
}
func NewSpanMapperFromUpdatable(req *UpdatableSpanMapper) *SpanMapper {
m := &SpanMapper{}
if req.FieldContext != (FieldContext{}) {
m.FieldContext = req.FieldContext
}
if req.Config != nil {
m.Config = *req.Config
}
if req.Enabled != nil {
m.Enabled = *req.Enabled
}
return m
}
func NewSpanMappersFromStorableSpanMappers(ss []*StorableSpanMapper) []*SpanMapper {
mappers := make([]*SpanMapper, len(ss))
for i, s := range ss {
mappers[i] = NewSpanMapperFromStorable(s)
}
return mappers
}
func NewGettableSpanMappers(m []*SpanMapper) *GettableSpanMappers {
return &GettableSpanMappers{Items: m}
}

View File

@@ -1,109 +0,0 @@
package spantypes
import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
ErrCodeMappingGroupNotFound = errors.MustNewCode("span_attribute_mapping_group_not_found")
ErrCodeMappingGroupAlreadyExists = errors.MustNewCode("span_attribute_mapping_group_already_exists")
)
// SpanMapperGroupCategory defaults will be llm, tool, agent but user can configure more as they want.
type SpanMapperGroupCategory valuer.String
// A group runs when any of the listed attribute/resource key patterns match.
type SpanMapperGroupCondition struct {
Attributes []string `json:"attributes" required:"true" nullable:"true"`
Resource []string `json:"resource" required:"true" nullable:"true"`
}
// SpanMapperGroup is the domain model for a span attribute mapping group.
type SpanMapperGroup struct {
types.TimeAuditable
types.UserAuditable
ID valuer.UUID `json:"id" required:"true"`
OrgID valuer.UUID `json:"orgId" required:"true"`
Name string `json:"name" required:"true"`
Category SpanMapperGroupCategory `json:"category" required:"true"`
Condition SpanMapperGroupCondition `json:"condition" required:"true"`
Enabled bool `json:"enabled" required:"true"`
}
// GettableSpanMapperGroup is the HTTP response representation of a mapping group.
type GettableSpanMapperGroup = SpanMapperGroup
type PostableSpanMapperGroup struct {
Name string `json:"name" required:"true"`
Category SpanMapperGroupCategory `json:"category" required:"true"`
Condition SpanMapperGroupCondition `json:"condition" required:"true"`
Enabled bool `json:"enabled"`
}
// UpdatableSpanMapperGroup is the HTTP request body for updating a mapping group.
// All fields are optional; only non-nil fields are applied.
type UpdatableSpanMapperGroup struct {
Name *string `json:"name,omitempty"`
Condition *SpanMapperGroupCondition `json:"condition,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
}
type ListSpanMapperGroupsQuery struct {
Category *SpanMapperGroupCategory `query:"category"`
Enabled *bool `query:"enabled"`
}
type GettableSpanMapperGroups struct {
Items []*GettableSpanMapperGroup `json:"items" required:"true" nullable:"false"`
}
func NewSpanMapperGroupFromStorable(s *StorableSpanMapperGroup) *SpanMapperGroup {
return &SpanMapperGroup{
TimeAuditable: s.TimeAuditable,
UserAuditable: s.UserAuditable,
ID: s.ID,
OrgID: s.OrgID,
Name: s.Name,
Category: s.Category,
Condition: s.Condition,
Enabled: s.Enabled,
}
}
func NewSpanMapperGroupFromPostable(p *PostableSpanMapperGroup) *SpanMapperGroup {
return &SpanMapperGroup{
Name: p.Name,
Category: p.Category,
Condition: p.Condition,
Enabled: p.Enabled,
}
}
func NewSpanMapperGroupFromUpdatable(u *UpdatableSpanMapperGroup) *SpanMapperGroup {
g := &SpanMapperGroup{}
if u.Name != nil {
g.Name = *u.Name
}
if u.Condition != nil {
g.Condition = *u.Condition
}
if u.Enabled != nil {
g.Enabled = *u.Enabled
}
return g
}
func NewSpanMapperGroupsFromStorableGroups(ss []*StorableSpanMapperGroup) []*SpanMapperGroup {
groups := make([]*SpanMapperGroup, len(ss))
for i, s := range ss {
groups[i] = NewSpanMapperGroupFromStorable(s)
}
return groups
}
func NewGettableSpanMapperGroups(g []*SpanMapperGroup) *GettableSpanMapperGroups {
return &GettableSpanMapperGroups{Items: g}
}

View File

@@ -1,87 +0,0 @@
package spantypes
import (
"database/sql/driver"
"encoding/json"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
type StorableSpanMapperGroup struct {
bun.BaseModel `bun:"table:span_mapper_group,alias:span_attribute_mapping_group"`
types.Identifiable
types.TimeAuditable
types.UserAuditable
OrgID valuer.UUID `bun:"org_id,type:text,notnull"`
Name string `bun:"name,type:text,notnull"`
Category SpanMapperGroupCategory `bun:"category,type:text,notnull"`
Condition SpanMapperGroupCondition `bun:"condition,type:jsonb,notnull"`
Enabled bool `bun:"enabled,notnull,default:true"`
}
type StorableSpanMapper struct {
bun.BaseModel `bun:"table:span_mapper,alias:span_attribute_mapping"`
types.Identifiable
types.TimeAuditable
types.UserAuditable
GroupID valuer.UUID `bun:"group_id,type:text,notnull"`
Name string `bun:"name,type:text,notnull"`
FieldContext FieldContext `bun:"field_context,type:text,notnull"`
Config SpanMapperConfig `bun:"config,type:jsonb,notnull"`
Enabled bool `bun:"enabled,notnull,default:true"`
}
func (c SpanMapperGroupCondition) Value() (driver.Value, error) {
b, err := json.Marshal(c)
if err != nil {
return nil, err
}
return string(b), nil
}
func (c *SpanMapperGroupCondition) Scan(src any) error {
var raw []byte
switch v := src.(type) {
case string:
raw = []byte(v)
case []byte:
raw = v
case nil:
*c = SpanMapperGroupCondition{}
return nil
default:
return errors.NewInternalf(errors.CodeInternal, "spanmapper: cannot scan %T into Condition", src)
}
return json.Unmarshal(raw, c)
}
func (m SpanMapperConfig) Value() (driver.Value, error) {
b, err := json.Marshal(m)
if err != nil {
return nil, err
}
return string(b), nil
}
func (m *SpanMapperConfig) Scan(src any) error {
var raw []byte
switch v := src.(type) {
case string:
raw = []byte(v)
case []byte:
raw = v
case nil:
*m = SpanMapperConfig{}
return nil
default:
return errors.NewInternalf(errors.CodeInternal, "spanmapper: cannot scan %T into MapperConfig", src)
}
return json.Unmarshal(raw, m)
}

View File

@@ -1,23 +0,0 @@
package spantypes
import (
"context"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Store interface {
// Group operations
ListSpanMapperGroups(ctx context.Context, orgID valuer.UUID, q *ListSpanMapperGroupsQuery) ([]*StorableSpanMapperGroup, error)
GetSpanMapperGroup(ctx context.Context, orgID, id valuer.UUID) (*StorableSpanMapperGroup, error)
CreateSpanMapperGroup(ctx context.Context, group *StorableSpanMapperGroup) error
UpdateSpanMapperGroup(ctx context.Context, group *StorableSpanMapperGroup) error
DeleteSpanMapperGroup(ctx context.Context, orgID, id valuer.UUID) error
// Mapper operations
ListSpanMappers(ctx context.Context, orgID, groupID valuer.UUID) ([]*StorableSpanMapper, error)
GetSpanMapper(ctx context.Context, orgID, groupID, id valuer.UUID) (*StorableSpanMapper, error)
CreateSpanMapper(ctx context.Context, mapper *StorableSpanMapper) error
UpdateSpanMapper(ctx context.Context, mapper *StorableSpanMapper) error
DeleteSpanMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error
}

View File

@@ -37,9 +37,9 @@ type TelemetryFieldKey struct {
FieldContext FieldContext `json:"fieldContext,omitzero"`
FieldDataType FieldDataType `json:"fieldDataType,omitzero"`
JSONPlan JSONAccessPlan `json:"-"`
Indexes []TelemetryFieldKeySkipIndex `json:"-"`
Materialized bool `json:"-"` // refers to promoted in case of body.... fields
JSONPlan JSONAccessPlan `json:"-"`
Indexes []JSONDataTypeIndex `json:"-"`
Materialized bool `json:"-"` // refers to promoted in case of body.... fields
Evolutions []*EvolutionEntry `json:"-"`
}
@@ -102,7 +102,7 @@ func (f TelemetryFieldKey) String() string {
if i > 0 {
sb.WriteString("; ")
}
fmt.Fprintf(&sb, "{type=%s, indexExpr=%s}", MappingFieldDataTypeToJSONDataType[index.FieldDataType].StringValue(), index.IndexExpression)
fmt.Fprintf(&sb, "{type=%s, columnExpr=%s, indexExpr=%s}", index.Type.StringValue(), index.ColumnExpression, index.IndexExpression)
}
sb.WriteString("]")
}
@@ -400,14 +400,3 @@ func NewFieldValueSelectorFromPostableFieldValueParams(params PostableFieldValue
return fieldValueSelector
}
type TelemetryFieldKeySkipIndex struct {
Name string `json:"name"` // Name is TelemetryFieldKey.Name not IndexName from ClickHouse
FieldContext FieldContext `json:"fieldContext,omitzero"`
FieldDataType FieldDataType `json:"fieldDataType,omitzero"`
BaseColumn string `json:"baseColumn"`
IndexName string `json:"indexName"`
IndexType string `json:"indexType"`
IndexExpression string `json:"indexExpression"`
Granularity int `json:"granularity"`
}

View File

@@ -1,5 +1,11 @@
package telemetrytypes
type JSONDataTypeIndex struct {
Type JSONDataType
ColumnExpression string
IndexExpression string
}
type JSONDataType struct {
str string // Store the correct case for ClickHouse
IsArray bool
@@ -26,17 +32,18 @@ var (
ArrayJSON = JSONDataType{"Array(JSON)", true, "JSON", false}
)
var MappingJSONDataTypeToFieldDataType = map[string]FieldDataType{
"String": FieldDataTypeString,
"Int64": FieldDataTypeInt64,
"Float64": FieldDataTypeFloat64,
"Bool": FieldDataTypeBool,
"Array(Nullable(String))": FieldDataTypeArrayString,
"Array(Nullable(Int64))": FieldDataTypeArrayInt64,
"Array(Nullable(Float64))": FieldDataTypeArrayFloat64,
"Array(Nullable(Bool))": FieldDataTypeArrayBool,
"Array(Dynamic)": FieldDataTypeArrayDynamic,
"Array(JSON)": FieldDataTypeArrayJSON,
var MappingStringToJSONDataType = map[string]JSONDataType{
"String": String,
"Int64": Int64,
"Float64": Float64,
"Bool": Bool,
"Dynamic": Dynamic,
"Array(Nullable(String))": ArrayString,
"Array(Nullable(Int64))": ArrayInt64,
"Array(Nullable(Float64))": ArrayFloat64,
"Array(Nullable(Bool))": ArrayBool,
"Array(Dynamic)": ArrayDynamic,
"Array(JSON)": ArrayJSON,
}
var MappingFieldDataTypeToJSONDataType = map[FieldDataType]JSONDataType{

View File

@@ -3,6 +3,7 @@ package telemetrytypes
import (
"context"
schemamigrator "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
)
@@ -34,7 +35,7 @@ type MetadataStore interface {
FetchTemporalityAndTypeMulti(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricNames ...string) (map[string]metrictypes.Temporality, map[string]metrictypes.Type, error)
// ListLogsJSONIndexes lists the JSON indexes for the logs table.
ListLogsJSONIndexes(ctx context.Context, filters ...string) ([]TelemetryFieldKeySkipIndex, error)
ListLogsJSONIndexes(ctx context.Context, filters ...string) (map[string][]schemamigrator.Index, error)
// ListPromotedPaths lists the promoted paths.
GetPromotedPaths(ctx context.Context, paths ...string) (map[string]bool, error)

View File

@@ -4,6 +4,7 @@ import (
"context"
"strings"
schemamigrator "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
)
@@ -17,7 +18,7 @@ type MockMetadataStore struct {
TemporalityMap map[string]metrictypes.Temporality
TypeMap map[string]metrictypes.Type
PromotedPathsMap map[string]bool
LogsJSONIndexes []telemetrytypes.TelemetryFieldKeySkipIndex
LogsJSONIndexesMap map[string][]schemamigrator.Index
ColumnEvolutionMetadataMap map[string][]*telemetrytypes.EvolutionEntry
LookupKeysMap map[telemetrytypes.MetricMetadataLookupKey]int64
// StaticFields holds signal-specific intrinsic field definitions (e.g. telemetrylogs.IntrinsicFields).
@@ -33,7 +34,7 @@ func NewMockMetadataStore() *MockMetadataStore {
TemporalityMap: make(map[string]metrictypes.Temporality),
TypeMap: make(map[string]metrictypes.Type),
PromotedPathsMap: make(map[string]bool),
LogsJSONIndexes: []telemetrytypes.TelemetryFieldKeySkipIndex{},
LogsJSONIndexesMap: make(map[string][]schemamigrator.Index),
ColumnEvolutionMetadataMap: make(map[string][]*telemetrytypes.EvolutionEntry),
LookupKeysMap: make(map[telemetrytypes.MetricMetadataLookupKey]int64),
StaticFields: make(map[string]telemetrytypes.TelemetryFieldKey),
@@ -368,8 +369,8 @@ func (m *MockMetadataStore) GetPromotedPaths(ctx context.Context, paths ...strin
}
// ListLogsJSONIndexes lists the JSON indexes for the logs table.
func (m *MockMetadataStore) ListLogsJSONIndexes(ctx context.Context, filters ...string) ([]telemetrytypes.TelemetryFieldKeySkipIndex, error) {
return m.LogsJSONIndexes, nil
func (m *MockMetadataStore) ListLogsJSONIndexes(ctx context.Context, filters ...string) (map[string][]schemamigrator.Index, error) {
return m.LogsJSONIndexesMap, nil
}
func (m *MockMetadataStore) updateColumnEvolutionMetadataForKeys(_ context.Context, keysToUpdate []*telemetrytypes.TelemetryFieldKey) map[string][]*telemetrytypes.EvolutionEntry {