Compare commits

...

153 Commits

Author SHA1 Message Date
Abhi kumar
ff8ec64c2e Merge branch 'main' into nv/heatmap-dashboard-panel 2026-09-18 13:24:42 +05:30
Abhi kumar
02069e0a5e Merge branch 'main' into nv/heatmap-dashboard-panel 2026-09-17 15:26:41 +05:30
Naman Verma
4260363b45 chore: revert test changes 2026-09-16 12:21:32 +05:30
Naman Verma
19c4aed27c Merge branch 'main' into nv/heatmap-dashboard-panel 2026-09-16 12:20:18 +05:30
Naman Verma
9f1b1476f4 Merge branch 'nv/heatmap' into nv/heatmap-dashboard-panel 2026-09-11 13:18:49 +05:30
Naman Verma
0bea671a2d Merge branch 'main' into nv/heatmap 2026-09-11 13:18:39 +05:30
Naman Verma
0b1695c9c2 chore: regenerate openapi spec 2026-09-11 13:16:29 +05:30
Naman Verma
b4f42cf388 feat(dashboards): add the heatmap panel 2026-09-11 13:15:25 +05:30
Naman Verma
65a8a58600 chore: regenerate api specs 2026-09-11 13:14:44 +05:30
Naman Verma
a3c90ab132 chore: move the heatmap dashboard panel to its own branch 2026-09-11 13:14:41 +05:30
Naman Verma
da4c5578d3 test: fix param in unit test 2026-09-11 13:09:23 +05:30
Naman Verma
bf01867c3c test: properly test for error messages 2026-09-11 12:58:26 +05:30
Naman Verma
41187a5fca chore: track full bucket details to be able to report the clashes in err msg 2026-09-11 12:38:14 +05:30
Naman Verma
d04dc6f7db test: add coarser scale in test 2026-09-11 12:15:11 +05:30
Naman Verma
71badcf65c chore: better comment 2026-09-11 12:03:26 +05:30
Naman Verma
68fe15d5fc chore: better comment 2026-09-11 12:02:43 +05:30
Naman Verma
f8aded8c8e test: use histogram sounding metric name for histogram unit tests 2026-09-11 12:00:45 +05:30
Naman Verma
f6a6793e96 test: remove logscale param from linear bucket in test 2026-09-11 11:57:45 +05:30
Naman Verma
452046d70c chore: remove POC 2026-09-11 11:45:43 +05:30
Naman Verma
2e9c9067f3 Merge branch 'main' into nv/heatmap 2026-09-11 11:45:01 +05:30
Naman Verma
2ddd45cb8c test: fix error msg assertion 2026-09-10 16:56:32 +05:30
Naman Verma
8809d8a7ac test: fix error msg assertion 2026-09-10 16:05:33 +05:30
Naman Verma
21edb05f2b fix: move bucket options to each query for dashboards 2026-09-10 16:04:17 +05:30
Naman Verma
f659205866 fix: make consume.go take in both upper and lower bounds 2026-09-10 14:38:15 +05:30
Naman Verma
215f6f8e0f fix: distinguish between no enabled and >1 enabled queries 2026-09-09 22:07:31 +05:30
Naman Verma
802f7e4a3d fix: more concise err message in promql no le case 2026-09-09 22:03:39 +05:30
Naman Verma
81f1105fbc fix: throw error if no le bucket if found in promql returned data 2026-09-09 21:56:43 +05:30
Naman Verma
575aa57426 Merge branch 'main' into nv/heatmap 2026-09-09 21:19:41 +05:30
Naman Verma
f75d3d8724 test: move rejection tests 2026-09-09 21:18:58 +05:30
Naman Verma
43538b92db fix: no value to be sent if values array is present 2026-09-09 19:57:51 +05:30
Naman Verma
ef65c2c08c chore: remove unneeded nil check 2026-09-09 19:46:47 +05:30
Naman Verma
8e41d3d30d Merge branch 'main' into nv/heatmap 2026-09-09 19:34:30 +05:30
Naman Verma
b249ab8275 fix: dont build heatmap bucket options for missing metrics 2026-09-09 12:25:40 +05:30
Naman Verma
61bca035a3 chore: use increase as time aggregation for regular percentiles as well 2026-09-09 11:55:40 +05:30
Naman Verma
9983b3344b Merge branch 'nv/heatmap' of https://github.com/SigNoz/signoz into nv/heatmap 2026-09-09 10:13:05 +05:30
Naman Verma
1313a289b6 Merge branch 'main' into nv/heatmap 2026-09-07 23:01:08 +05:30
Naman Verma
82a789c19e test: fix pylint in integration test 2026-09-07 21:01:03 +05:30
Naman Verma
ec09873573 chore: push temporary poc for testing 2026-09-07 21:01:03 +05:30
Naman Verma
00cc773792 test: add integration tests 2026-09-07 21:01:03 +05:30
Naman Verma
cf5dd79216 fix: return unsupported for logs and traces 2026-09-07 21:01:03 +05:30
Naman Verma
6546a0abc6 test: delete heatmap UTs (to be covered in integration tests) 2026-09-07 21:01:03 +05:30
Naman Verma
3874f3a6ec test: delete heatmap validation UTs (to be covered in integration tests) 2026-09-07 21:01:03 +05:30
Naman Verma
86db0fd26b chore: var renames 2026-09-07 21:01:03 +05:30
Naman Verma
f382a958db chore: code refactoring of bucketFormulaOutputAsHeatmap 2026-09-07 21:01:03 +05:30
Naman Verma
7c3481b230 chore: comments, code structure changes and renamings in resolveHeatmapBucketAxis 2026-09-07 21:01:03 +05:30
Naman Verma
24f3116527 chore: map var renames 2026-09-07 21:01:03 +05:30
Naman Verma
17f0f49398 chore: some code movement and clarity around ReindexValuesToNewUpperBounds 2026-09-07 21:01:03 +05:30
Naman Verma
f681bacdcb chore: method rename 2026-09-07 21:01:03 +05:30
Naman Verma
ded4ed3d76 chore: add comment for logs and traces supporting only 1 aggregation 2026-09-07 21:01:03 +05:30
Naman Verma
1f84f9c166 chore: remove comment 2026-09-07 21:01:03 +05:30
Naman Verma
87127b580c chore: upperbound instead of boundary 2026-09-07 21:01:03 +05:30
Naman Verma
fc4bd13fce test: remove unit tests that can be covered in integration tests 2026-09-07 21:01:03 +05:30
Naman Verma
075a4b164d chore: revert UT change 2026-09-07 21:01:03 +05:30
Naman Verma
2170189d14 chore: shorten comments 2026-09-07 21:01:03 +05:30
Naman Verma
b7720b655e chore: shorten comments 2026-09-07 21:01:03 +05:30
Naman Verma
f34973b897 chore: use the term upper bound instead of boundary 2026-09-07 21:01:03 +05:30
Naman Verma
d9d041a8fa fix: use separate typedef for cumulative column 2026-09-07 21:01:03 +05:30
Naman Verma
a0e66e3592 fix: return empty instead of err if promql resp does not have le 2026-09-07 21:01:03 +05:30
Naman Verma
a0dff26fbc test: remove unit tests that can be covered in integration tests 2026-09-07 21:01:03 +05:30
Naman Verma
db0b1b2b2a chore: remove capacity vars for an easier read 2026-09-07 21:01:03 +05:30
Naman Verma
fd8ed18592 chore: better var name 2026-09-07 21:01:03 +05:30
Naman Verma
392b5826fd chore: better comment 2026-09-07 21:01:03 +05:30
Naman Verma
4b4023d706 chore: move HeatmapBucketColumn 2026-09-07 21:01:03 +05:30
Naman Verma
f8d803d787 fix: allow other panel types in heatmap 2026-09-07 21:01:03 +05:30
Naman Verma
a33320c665 test: add unit tests for cumulative metrics 2026-09-07 21:01:03 +05:30
Naman Verma
695a481989 fix: use heatmapBucketing.LogScale correctly 2026-09-07 21:01:03 +05:30
Naman Verma
3fc763b95b chore: break down method for easier reading 2026-09-07 21:01:03 +05:30
Naman Verma
027b65574d chore: shorten comments 2026-09-07 21:01:03 +05:30
Naman Verma
43ec98b309 chore: rename method name 2026-09-07 21:01:03 +05:30
Naman Verma
a196a436be chore: code movement 2026-09-07 21:01:03 +05:30
Naman Verma
3f456b6602 chore: add separate file for heatmap accumulator 2026-09-07 21:01:03 +05:30
Naman Verma
f777e068c3 chore: reduce comment size 2026-09-07 21:01:03 +05:30
Naman Verma
5a690695e6 chore: split toResult method into two 2026-09-07 21:01:03 +05:30
Naman Verma
bf362d15db chore: shorten comment 2026-09-07 21:01:03 +05:30
Naman Verma
7e0f70ffa7 chore: remove unneeded comment 2026-09-07 21:01:03 +05:30
Naman Verma
590046adf3 fix: add heatmap to hasData check 2026-09-07 21:01:03 +05:30
Naman Verma
106cb9f6c2 chore: shorten comment 2026-09-07 21:01:03 +05:30
Naman Verma
f95699b1f7 chore: method name change 2026-09-07 21:01:03 +05:30
Naman Verma
1e75b66379 chore: method name change 2026-09-07 21:01:03 +05:30
Naman Verma
b80013d075 fix: return 501 for exp histogram instead of 400 2026-09-07 21:01:03 +05:30
Naman Verma
4a6e25708d chore: remove unneeded comments 2026-09-07 21:01:03 +05:30
Naman Verma
3b8f9a7830 chore: move MaxNumBuckets const to where it is actually used 2026-09-07 21:01:03 +05:30
Naman Verma
68bff5b053 chore: remove unneeded comments 2026-09-07 21:01:03 +05:30
Naman Verma
732c8ae18a chore: remove unneeded comments 2026-09-07 21:01:03 +05:30
Naman Verma
6911630c2a fix: dont allow bucket options in non heatmap requests 2026-09-07 21:01:03 +05:30
Naman Verma
2d78d8add9 chore: move valid bucket kinds to additional part of err 2026-09-07 21:01:03 +05:30
Naman Verma
8e47c7d337 chore: move consts to where they are actually used 2026-09-07 21:01:03 +05:30
Naman Verma
991e734c33 chore: minor code movement 2026-09-07 21:01:03 +05:30
Naman Verma
40c881b8f5 chore: remove unneeded comment 2026-09-07 21:01:03 +05:30
Naman Verma
a2c8dd5275 chore: remove unneeded comment 2026-09-07 21:01:03 +05:30
Naman Verma
32ae7a26d5 chore: shorten comment 2026-09-07 21:01:03 +05:30
Naman Verma
4067b04fa2 fix: update dashboard schema to latest spec 2026-09-07 21:01:03 +05:30
Naman Verma
70409f5ee3 fix: make ResolveHeatmapBucketing a method on MetricAggregation 2026-09-07 21:01:03 +05:30
Naman Verma
5d9953bb7e feat: add heatmap support in query and dashboards 2026-09-07 21:01:03 +05:30
Naman Verma
edfff0ee72 test: fix pylint in integration test 2026-09-07 16:42:23 +05:30
Naman Verma
44556060a3 chore: push temporary poc for testing 2026-09-07 15:54:09 +05:30
Naman Verma
545e0342d2 test: add integration tests 2026-09-07 14:57:47 +05:30
Naman Verma
2f559f34d3 fix: return unsupported for logs and traces 2026-09-06 12:14:39 +05:30
Naman Verma
e4d90b91b7 test: delete heatmap UTs (to be covered in integration tests) 2026-09-06 05:07:18 +05:30
Naman Verma
f33616a598 test: delete heatmap validation UTs (to be covered in integration tests) 2026-09-06 04:51:05 +05:30
Naman Verma
2ac94fc2fc chore: var renames 2026-09-06 04:46:39 +05:30
Naman Verma
7292a6946b chore: code refactoring of bucketFormulaOutputAsHeatmap 2026-09-06 04:32:54 +05:30
Naman Verma
dff49a4135 chore: comments, code structure changes and renamings in resolveHeatmapBucketAxis 2026-09-06 04:09:09 +05:30
Naman Verma
690c207519 chore: map var renames 2026-09-06 03:19:59 +05:30
Naman Verma
d9b6d1f425 chore: some code movement and clarity around ReindexValuesToNewUpperBounds 2026-09-06 03:18:01 +05:30
Naman Verma
1eb974d91a chore: method rename 2026-09-06 02:55:54 +05:30
Naman Verma
1141f6bf14 chore: add comment for logs and traces supporting only 1 aggregation 2026-09-06 02:50:12 +05:30
Naman Verma
a3fb926726 chore: remove comment 2026-09-06 02:25:27 +05:30
Naman Verma
fe1e86f385 chore: upperbound instead of boundary 2026-09-06 02:12:39 +05:30
Naman Verma
e30d9a1a14 test: remove unit tests that can be covered in integration tests 2026-09-06 01:51:03 +05:30
Naman Verma
c9a345713c chore: revert UT change 2026-09-06 01:49:52 +05:30
Naman Verma
22f8eba44a chore: shorten comments 2026-09-06 01:41:53 +05:30
Naman Verma
e8ad9044d5 chore: shorten comments 2026-09-06 01:40:08 +05:30
Naman Verma
576cd5052a chore: use the term upper bound instead of boundary 2026-09-06 01:37:33 +05:30
Naman Verma
4214ccf93d fix: use separate typedef for cumulative column 2026-09-06 01:34:44 +05:30
Naman Verma
63f5c17238 fix: return empty instead of err if promql resp does not have le 2026-09-06 01:25:53 +05:30
Naman Verma
fd6abd67dd test: remove unit tests that can be covered in integration tests 2026-09-05 16:25:23 +05:30
Naman Verma
a78c35d61c chore: remove capacity vars for an easier read 2026-09-05 16:19:46 +05:30
Naman Verma
b2c4916a86 chore: better var name 2026-09-05 16:08:02 +05:30
Naman Verma
feff1a0179 chore: better comment 2026-09-05 15:53:47 +05:30
Naman Verma
eafac1be69 chore: move HeatmapBucketColumn 2026-09-05 15:35:14 +05:30
Naman Verma
510d6985d0 fix: allow other panel types in heatmap 2026-09-05 15:23:15 +05:30
Naman Verma
758aacb065 test: add unit tests for cumulative metrics 2026-09-05 15:13:11 +05:30
Naman Verma
5599c352d0 fix: use heatmapBucketing.LogScale correctly 2026-09-05 14:29:47 +05:30
Naman Verma
96d0a2340c chore: break down method for easier reading 2026-09-05 14:19:11 +05:30
Naman Verma
c6b415ad04 chore: shorten comments 2026-09-05 14:14:39 +05:30
Naman Verma
9b922f297f chore: rename method name 2026-09-05 13:53:08 +05:30
Naman Verma
e81a4c4046 chore: code movement 2026-09-05 13:44:11 +05:30
Naman Verma
9ba13848ea chore: add separate file for heatmap accumulator 2026-09-05 13:16:18 +05:30
Naman Verma
2578f25dd4 chore: reduce comment size 2026-09-05 12:46:35 +05:30
Naman Verma
82352973bb chore: split toResult method into two 2026-09-05 12:36:47 +05:30
Naman Verma
3eb55692f1 chore: shorten comment 2026-09-05 04:04:45 +05:30
Naman Verma
61ceb6d300 chore: remove unneeded comment 2026-09-05 04:03:43 +05:30
Naman Verma
e33fc20579 fix: add heatmap to hasData check 2026-09-05 03:50:44 +05:30
Naman Verma
b130a61049 chore: shorten comment 2026-09-05 03:25:14 +05:30
Naman Verma
132e9bb740 chore: method name change 2026-09-05 03:15:13 +05:30
Naman Verma
9d577731e1 chore: method name change 2026-09-05 03:07:14 +05:30
Naman Verma
4d54568fe1 fix: return 501 for exp histogram instead of 400 2026-09-05 03:04:41 +05:30
Naman Verma
6f82624523 chore: remove unneeded comments 2026-09-05 02:53:15 +05:30
Naman Verma
da0bc4f438 chore: move MaxNumBuckets const to where it is actually used 2026-09-05 02:51:29 +05:30
Naman Verma
2dcb764632 chore: remove unneeded comments 2026-09-05 02:50:27 +05:30
Naman Verma
1bc25e16b8 chore: remove unneeded comments 2026-09-05 02:50:03 +05:30
Naman Verma
eebc6b400d fix: dont allow bucket options in non heatmap requests 2026-09-05 02:49:41 +05:30
Naman Verma
30c4508113 chore: move valid bucket kinds to additional part of err 2026-09-05 02:38:00 +05:30
Naman Verma
c4fe619920 chore: move consts to where they are actually used 2026-09-05 02:33:59 +05:30
Naman Verma
d597adddda chore: minor code movement 2026-09-05 02:31:10 +05:30
Naman Verma
9a05f33e7a chore: remove unneeded comment 2026-09-05 02:25:48 +05:30
Naman Verma
3812937c6f chore: remove unneeded comment 2026-09-05 02:25:29 +05:30
Naman Verma
34aa719cba chore: shorten comment 2026-09-05 02:24:15 +05:30
Naman Verma
cdfce4c4ab fix: update dashboard schema to latest spec 2026-09-05 02:12:34 +05:30
Naman Verma
d8ef672f83 Merge branch 'main' into nv/heatmap 2026-09-05 02:00:57 +05:30
Naman Verma
00efffc127 fix: make ResolveHeatmapBucketing a method on MetricAggregation 2026-09-03 13:50:09 +05:30
Naman Verma
dec922a83f feat: add heatmap support in query and dashboards 2026-09-03 12:07:27 +05:30
5 changed files with 602 additions and 1 deletions

View File

@@ -3552,6 +3552,79 @@ components:
hide:
type: boolean
type: object
DashboardtypesHeatmapAxes:
properties:
yScale:
$ref: '#/components/schemas/DashboardtypesHeatmapYScale'
type: object
DashboardtypesHeatmapChartAppearance:
properties:
colors:
$ref: '#/components/schemas/DashboardtypesHeatmapColors'
type: object
DashboardtypesHeatmapColorMode:
enum:
- palette
- opacity
type: string
DashboardtypesHeatmapColorScale:
enum:
- log
- sqrt
- linear
type: string
DashboardtypesHeatmapColors:
properties:
fill:
type: string
maxCount:
nullable: true
type: number
minCount:
nullable: true
type: number
mode:
$ref: '#/components/schemas/DashboardtypesHeatmapColorMode'
palette:
$ref: '#/components/schemas/DashboardtypesHeatmapPalette'
scale:
$ref: '#/components/schemas/DashboardtypesHeatmapColorScale'
steps:
type: integer
type: object
DashboardtypesHeatmapPalette:
enum:
- ice
- moss
- rust
- graphite
- ember
- lagoon
- orchid
- verdant
- lava
- beacon
type: string
DashboardtypesHeatmapPanelSpec:
properties:
axes:
$ref: '#/components/schemas/DashboardtypesHeatmapAxes'
chartAppearance:
$ref: '#/components/schemas/DashboardtypesHeatmapChartAppearance'
formatting:
$ref: '#/components/schemas/DashboardtypesPanelFormatting'
legend:
$ref: '#/components/schemas/DashboardtypesLegend'
visualization:
$ref: '#/components/schemas/DashboardtypesBasicVisualization'
type: object
DashboardtypesHeatmapYScale:
enum:
- auto
- linear
- log
- symlog
type: string
DashboardtypesHistogramBuckets:
properties:
bucketCount:
@@ -3904,6 +3977,7 @@ components:
discriminator:
mapping:
signoz/BarChartPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBarChartPanelSpec'
signoz/HeatmapPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHeatmapPanelSpec'
signoz/HistogramPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHistogramPanelSpec'
signoz/ListPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesListPanelSpec'
signoz/NumberPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesNumberPanelSpec'
@@ -3921,6 +3995,7 @@ components:
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHistogramPanelSpec'
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesListPanelSpec'
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTextPanelSpec'
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHeatmapPanelSpec'
type: object
DashboardtypesPanelPluginKind:
enum:
@@ -3932,6 +4007,7 @@ components:
- signoz/HistogramPanel
- signoz/ListPanel
- signoz/TextPanel
- signoz/HeatmapPanel
type: string
DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBarChartPanelSpec:
properties:
@@ -3945,6 +4021,18 @@ components:
- kind
- spec
type: object
DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHeatmapPanelSpec:
properties:
kind:
enum:
- signoz/HeatmapPanel
type: string
spec:
$ref: '#/components/schemas/DashboardtypesHeatmapPanelSpec'
required:
- kind
- spec
type: object
DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHistogramPanelSpec:
properties:
kind:

View File

@@ -8,6 +8,7 @@ import (
"testing"
"github.com/SigNoz/signoz/pkg/errors"
qb "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/perses/spec/go/dashboard"
"github.com/stretchr/testify/assert"
@@ -524,6 +525,149 @@ func TestInvalidateUnknownPluginKind(t *testing.T) {
}
}
// TestHeatmapPanelQueryKinds pins the panel allowlist to what validateHeatmap
// accepts in querybuildertypesv5: everything but a trace operator.
func TestHeatmapPanelQueryKinds(t *testing.T) {
testCases := []struct {
description string
queryPluginKind string
queryPluginSpec string
expectedAllowed bool
}{
{
description: "a metrics builder query is allowed",
queryPluginKind: "signoz/BuilderQuery",
queryPluginSpec: `{"name": "A", "signal": "metrics", "aggregations": [
{"metricName": "http.server.request.duration", "timeAggregation": "increase", "spaceAggregation": "sum"}
]}`,
expectedAllowed: true,
},
{
description: "a promql query is allowed",
queryPluginKind: "signoz/PromQLQuery",
queryPluginSpec: `{"name": "A", "query": "sum by (le) (increase(signoz_latency_bucket[5m]))"}`,
expectedAllowed: true,
},
{
description: "a clickhouse query is allowed",
queryPluginKind: "signoz/ClickHouseSQL",
queryPluginSpec: `{"name": "A", "query": "SELECT ts, bucket, value FROM cells"}`,
expectedAllowed: true,
},
{
description: "a formula is allowed",
queryPluginKind: "signoz/Formula",
queryPluginSpec: `{"name": "F1", "expression": "A / B"}`,
expectedAllowed: true,
},
{
description: "a composite query is allowed, since a formula needs its disabled inputs alongside it",
queryPluginKind: "signoz/CompositeQuery",
queryPluginSpec: `{"queries": [
{"type": "builder_query", "spec": {"name": "A", "signal": "metrics", "disabled": true, "aggregations": [
{"metricName": "http.server.request.duration", "timeAggregation": "increase", "spaceAggregation": "sum"}
]}},
{"type": "builder_formula", "spec": {"name": "F1", "expression": "A * 2"}}
]}`,
expectedAllowed: true,
},
{
description: "a trace operator is refused",
queryPluginKind: "signoz/TraceOperator",
queryPluginSpec: `{"name": "T1", "expression": "A => B"}`,
expectedAllowed: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.description, func(t *testing.T) {
data := fmt.Sprintf(`{
"variables": [],
"panels": {
"p1": {
"kind": "Panel",
"spec": {
"links": [],
"plugin": {"kind": "signoz/HeatmapPanel", "spec": {}},
"queries": [{
"kind": "heatmap",
"spec": {
"plugin": {"kind": %q, "spec": %s}
}
}]
}
}
},
"links": [],
"layouts": []
}`, testCase.queryPluginKind, testCase.queryPluginSpec)
_, err := unmarshalDashboard([]byte(data))
if testCase.expectedAllowed {
require.NoError(t, err)
return
}
require.Error(t, err)
assert.Contains(t, err.Error(), "is not supported by panel kind")
})
}
}
func TestValidateHeatmapDashboard(t *testing.T) {
data, err := os.ReadFile("testdata/perses_heatmap_panel.json")
require.NoError(t, err, "reading example file")
spec, err := unmarshalDashboard(data)
require.NoError(t, err, "unmarshal and validate failed")
require.IsType(t, &HeatmapPanelSpec{}, spec.Panels["p1"].Spec.Plugin.Spec)
panelSpec := spec.Panels["p1"].Spec.Plugin.Spec.(*HeatmapPanelSpec)
assert.Equal(t, "log", panelSpec.Axes.YScale.ValueOrDefault())
assert.Equal(t, "ember", panelSpec.ChartAppearance.Colors.Palette.ValueOrDefault())
assert.Equal(t, "sqrt", panelSpec.ChartAppearance.Colors.Scale.ValueOrDefault())
assert.Equal(t, 8, panelSpec.ChartAppearance.Colors.Steps)
dashboard := &DashboardV2{Spec: *spec}
request, err := dashboard.GetPanelQuery(1, 2, "p1")
require.NoError(t, err, "building the panel's query failed")
assert.Equal(t, qb.RequestTypeHeatmap, request.RequestType)
require.Len(t, request.CompositeQuery.Queries, 3)
numerator, ok := request.CompositeQuery.Queries[0].Spec.(qb.QueryBuilderQuery[qb.MetricAggregation])
require.True(t, ok, "expected a metrics builder query")
assert.True(t, numerator.Disabled)
require.NotNil(t, numerator.BucketOptions)
require.IsType(t, qb.LogBucketsSpec{}, numerator.BucketOptions.Spec)
assert.Equal(t, 4, *numerator.BucketOptions.Spec.(qb.LogBucketsSpec).Scale)
denominator, ok := request.CompositeQuery.Queries[1].Spec.(qb.QueryBuilderQuery[qb.MetricAggregation])
require.True(t, ok, "expected a metrics builder query")
assert.True(t, denominator.Disabled)
require.NotNil(t, denominator.BucketOptions)
require.IsType(t, qb.LinearBucketsSpec{}, denominator.BucketOptions.Spec)
assert.Equal(t, float64(1000), denominator.BucketOptions.Spec.(qb.LinearBucketsSpec).MaxValue)
formula, ok := request.CompositeQuery.Queries[2].Spec.(qb.QueryBuilderFormula)
require.True(t, ok, "expected a formula")
require.NotNil(t, formula.BucketOptions)
assert.Equal(t, qb.BucketsKindLog, formula.BucketOptions.Kind)
require.IsType(t, qb.LogBucketsSpec{}, formula.BucketOptions.Spec)
assert.Equal(t, 2, *formula.BucketOptions.Spec.(qb.LogBucketsSpec).Scale)
require.NoError(t, request.Validate(), "the request built from the panel is not a valid heatmap request")
// the panel read back out of storage draws the same heatmap
stored, err := json.Marshal(spec)
require.NoError(t, err, "marshal dashboard failed")
reread, err := unmarshalDashboard(stored)
require.NoError(t, err, "the stored dashboard does not validate")
rereadRequest, err := (&DashboardV2{Spec: *reread}).GetPanelQuery(1, 2, "p1")
require.NoError(t, err, "building the stored panel's query failed")
assert.Equal(t, request, rereadRequest)
}
func TestInvalidateOneInvalidPanel(t *testing.T) {
data := []byte(`{
"variables": [],

View File

@@ -36,6 +36,7 @@ func (PanelPlugin) PrepareJSONSchema(s *jsonschema.Schema) error {
string(PanelKindHistogram): schemaRef("DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHistogramPanelSpec"),
string(PanelKindList): schemaRef("DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesListPanelSpec"),
string(PanelKindText): schemaRef("DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTextPanelSpec"),
string(PanelKindHeatmap): schemaRef("DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHeatmapPanelSpec"),
})
}
@@ -67,6 +68,7 @@ func (PanelPlugin) JSONSchemaOneOf() []any {
PanelPluginVariant[HistogramPanelSpec]{Kind: string(PanelKindHistogram)},
PanelPluginVariant[ListPanelSpec]{Kind: string(PanelKindList)},
PanelPluginVariant[TextPanelSpec]{Kind: string(PanelKindText)},
PanelPluginVariant[HeatmapPanelSpec]{Kind: string(PanelKindHeatmap)},
}
}
@@ -231,6 +233,7 @@ var (
PanelKindHistogram: func() any { return new(HistogramPanelSpec) },
PanelKindList: func() any { return new(ListPanelSpec) },
PanelKindText: func() any { return new(TextPanelSpec) },
PanelKindHeatmap: func() any { return new(HeatmapPanelSpec) },
}
queryPluginSpecs = map[QueryPluginKind]func() any{
QueryKindBuilder: func() any { return new(BuilderQuerySpec) },
@@ -254,6 +257,7 @@ var (
PanelKindTable: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindClickHouseSQL},
PanelKindList: {QueryKindBuilder},
PanelKindText: {},
PanelKindHeatmap: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindPromQL, QueryKindClickHouseSQL},
}
)

View File

@@ -174,10 +174,11 @@ const (
PanelKindHistogram PanelPluginKind = "signoz/HistogramPanel"
PanelKindList PanelPluginKind = "signoz/ListPanel"
PanelKindText PanelPluginKind = "signoz/TextPanel"
PanelKindHeatmap PanelPluginKind = "signoz/HeatmapPanel"
)
func (PanelPluginKind) Enum() []any {
return []any{PanelKindTimeSeries, PanelKindBarChart, PanelKindNumber, PanelKindPieChart, PanelKindTable, PanelKindHistogram, PanelKindList, PanelKindText}
return []any{PanelKindTimeSeries, PanelKindBarChart, PanelKindNumber, PanelKindPieChart, PanelKindTable, PanelKindHistogram, PanelKindList, PanelKindText, PanelKindHeatmap}
}
func (k PanelPluginKind) rendersWithoutQuery() bool {
@@ -242,6 +243,56 @@ type ListPanelSpec struct {
SelectFields []telemetrytypes.TelemetryFieldKey `json:"selectFields,omitzero" validate:"dive"`
}
type HeatmapPanelSpec struct {
Visualization BasicVisualization `json:"visualization"`
Formatting PanelFormatting `json:"formatting"`
Axes HeatmapAxes `json:"axes"`
Legend Legend `json:"legend"`
ChartAppearance HeatmapChartAppearance `json:"chartAppearance"`
}
// HeatmapAxes carries only the Y scale. The shared Axes type models a value
// axis with soft bounds, where a heatmap's Y axis is the bucket boundaries the
// response already fixed.
type HeatmapAxes struct {
YScale HeatmapYScale `json:"yScale"`
}
type HeatmapChartAppearance struct {
Colors HeatmapColors `json:"colors"`
}
type HeatmapColors struct {
Mode HeatmapColorMode `json:"mode"`
Palette HeatmapPalette `json:"palette"`
Scale HeatmapColorScale `json:"scale"`
Steps int `json:"steps" validate:"omitempty,min=2,max=128"`
// MinCount and MaxCount clamp the colour scale; nil derives them from the
// grid, 0 and the highest count in it.
MinCount *float64 `json:"minCount"`
MaxCount *float64 `json:"maxCount"`
// Fill applies in opacity mode; empty means the selected group's legend colour.
Fill string `json:"fill"`
}
func (c *HeatmapColors) UnmarshalJSON(data []byte) error {
type alias HeatmapColors
var tmp alias
if err := json.Unmarshal(data, &tmp); err != nil {
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "invalid heatmap colors")
}
*c = HeatmapColors(tmp)
return c.validate()
}
func (c HeatmapColors) validate() error {
if c.MinCount != nil && c.MaxCount != nil && *c.MinCount > *c.MaxCount {
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput,
"heatmap colors.minCount %v is greater than colors.maxCount %v", *c.MinCount, *c.MaxCount)
}
return nil
}
type TextPanelSpec struct {
Mode TextMode `json:"mode"`
Text string `json:"text"`
@@ -846,3 +897,168 @@ func (p *PrecisionOption) UnmarshalJSON(data []byte) error {
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "invalid precision option %q: must be `0`, `1`, `2`, `3`, `4`, or `full`", v)
}
}
type HeatmapColorMode struct{ valuer.String }
var (
HeatmapColorModePalette = HeatmapColorMode{valuer.NewString("palette")} // default
HeatmapColorModeOpacity = HeatmapColorMode{valuer.NewString("opacity")}
)
func (HeatmapColorMode) Enum() []any {
return []any{HeatmapColorModePalette, HeatmapColorModeOpacity}
}
func (m HeatmapColorMode) ValueOrDefault() string {
if m.IsZero() {
return HeatmapColorModePalette.StringValue()
}
return m.StringValue()
}
func (m HeatmapColorMode) MarshalJSON() ([]byte, error) {
return json.Marshal(m.ValueOrDefault())
}
func (m *HeatmapColorMode) UnmarshalJSON(data []byte) error {
var v string
if err := json.Unmarshal(data, &v); err != nil {
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "invalid heatmap color mode: must be a string, one of `palette` or `opacity`")
}
mode := HeatmapColorMode{valuer.NewString(v)}
switch mode {
case HeatmapColorModePalette, HeatmapColorModeOpacity:
*m = mode
return nil
default:
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "invalid heatmap color mode %q: must be `palette` or `opacity`", v)
}
}
type HeatmapPalette struct{ valuer.String }
var (
HeatmapPaletteIce = HeatmapPalette{valuer.NewString("ice")}
HeatmapPaletteMoss = HeatmapPalette{valuer.NewString("moss")}
HeatmapPaletteRust = HeatmapPalette{valuer.NewString("rust")}
HeatmapPaletteGraphite = HeatmapPalette{valuer.NewString("graphite")}
HeatmapPaletteEmber = HeatmapPalette{valuer.NewString("ember")}
HeatmapPaletteLagoon = HeatmapPalette{valuer.NewString("lagoon")}
HeatmapPaletteOrchid = HeatmapPalette{valuer.NewString("orchid")}
HeatmapPaletteVerdant = HeatmapPalette{valuer.NewString("verdant")}
HeatmapPaletteLava = HeatmapPalette{valuer.NewString("lava")} // default
HeatmapPaletteBeacon = HeatmapPalette{valuer.NewString("beacon")}
)
func (HeatmapPalette) Enum() []any {
return []any{
HeatmapPaletteIce, HeatmapPaletteMoss, HeatmapPaletteRust, HeatmapPaletteGraphite,
HeatmapPaletteEmber, HeatmapPaletteLagoon, HeatmapPaletteOrchid, HeatmapPaletteVerdant,
HeatmapPaletteLava, HeatmapPaletteBeacon,
}
}
func (p HeatmapPalette) ValueOrDefault() string {
if p.IsZero() {
return HeatmapPaletteLava.StringValue()
}
return p.StringValue()
}
func (p HeatmapPalette) MarshalJSON() ([]byte, error) {
return json.Marshal(p.ValueOrDefault())
}
func (p *HeatmapPalette) UnmarshalJSON(data []byte) error {
var v string
if err := json.Unmarshal(data, &v); err != nil {
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "invalid heatmap palette: must be a string, one of `ice`, `moss`, `rust`, `graphite`, `ember`, `lagoon`, `orchid`, `verdant`, `lava`, or `beacon`")
}
palette := HeatmapPalette{valuer.NewString(v)}
switch palette {
case HeatmapPaletteIce, HeatmapPaletteMoss, HeatmapPaletteRust, HeatmapPaletteGraphite,
HeatmapPaletteEmber, HeatmapPaletteLagoon, HeatmapPaletteOrchid, HeatmapPaletteVerdant,
HeatmapPaletteLava, HeatmapPaletteBeacon:
*p = palette
return nil
default:
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "invalid heatmap palette %q: must be `ice`, `moss`, `rust`, `graphite`, `ember`, `lagoon`, `orchid`, `verdant`, `lava`, or `beacon`", v)
}
}
type HeatmapYScale struct{ valuer.String }
var (
HeatmapYScaleAuto = HeatmapYScale{valuer.NewString("auto")} // default
HeatmapYScaleLinear = HeatmapYScale{valuer.NewString("linear")}
HeatmapYScaleLog = HeatmapYScale{valuer.NewString("log")}
HeatmapYScaleSymlog = HeatmapYScale{valuer.NewString("symlog")}
)
func (HeatmapYScale) Enum() []any {
return []any{HeatmapYScaleAuto, HeatmapYScaleLinear, HeatmapYScaleLog, HeatmapYScaleSymlog}
}
func (s HeatmapYScale) ValueOrDefault() string {
if s.IsZero() {
return HeatmapYScaleAuto.StringValue()
}
return s.StringValue()
}
func (s HeatmapYScale) MarshalJSON() ([]byte, error) {
return json.Marshal(s.ValueOrDefault())
}
func (s *HeatmapYScale) UnmarshalJSON(data []byte) error {
var v string
if err := json.Unmarshal(data, &v); err != nil {
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "invalid heatmap y scale: must be a string, one of `auto`, `linear`, `log`, or `symlog`")
}
scale := HeatmapYScale{valuer.NewString(v)}
switch scale {
case HeatmapYScaleAuto, HeatmapYScaleLinear, HeatmapYScaleLog, HeatmapYScaleSymlog:
*s = scale
return nil
default:
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "invalid heatmap y scale %q: must be `auto`, `linear`, `log`, or `symlog`", v)
}
}
type HeatmapColorScale struct{ valuer.String }
var (
HeatmapColorScaleLog = HeatmapColorScale{valuer.NewString("log")} // default
HeatmapColorScaleSqrt = HeatmapColorScale{valuer.NewString("sqrt")}
HeatmapColorScaleLinear = HeatmapColorScale{valuer.NewString("linear")}
)
func (HeatmapColorScale) Enum() []any {
return []any{HeatmapColorScaleLog, HeatmapColorScaleSqrt, HeatmapColorScaleLinear}
}
func (s HeatmapColorScale) ValueOrDefault() string {
if s.IsZero() {
return HeatmapColorScaleLog.StringValue()
}
return s.StringValue()
}
func (s HeatmapColorScale) MarshalJSON() ([]byte, error) {
return json.Marshal(s.ValueOrDefault())
}
func (s *HeatmapColorScale) UnmarshalJSON(data []byte) error {
var v string
if err := json.Unmarshal(data, &v); err != nil {
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "invalid heatmap color scale: must be a string, one of `log`, `sqrt`, or `linear`")
}
scale := HeatmapColorScale{valuer.NewString(v)}
switch scale {
case HeatmapColorScaleLog, HeatmapColorScaleSqrt, HeatmapColorScaleLinear:
*s = scale
return nil
default:
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "invalid heatmap color scale %q: must be `log`, `sqrt`, or `linear`", v)
}
}

View File

@@ -0,0 +1,149 @@
{
"display": {
"name": "latency",
"description": "how request duration is distributed"
},
"variables": [],
"panels": {
"p1": {
"kind": "Panel",
"spec": {
"display": {
"name": "request duration",
"description": ""
},
"plugin": {
"kind": "signoz/HeatmapPanel",
"spec": {
"visualization": {
"timePreference": "global_time"
},
"formatting": {
"unit": "s",
"decimalPrecision": "3"
},
"axes": {
"yScale": "log"
},
"legend": {
"position": "right",
"mode": "table"
},
"chartAppearance": {
"colors": {
"mode": "palette",
"palette": "ember",
"scale": "sqrt",
"steps": 8,
"minCount": 0,
"maxCount": 500
}
}
}
},
"links": [],
"queries": [
{
"kind": "heatmap",
"spec": {
"plugin": {
"kind": "signoz/CompositeQuery",
"spec": {
"queries": [
{
"type": "builder_query",
"spec": {
"name": "A",
"signal": "metrics",
"disabled": true,
"stepInterval": 60,
"aggregations": [
{
"metricName": "http.server.request.duration",
"timeAggregation": "increase",
"spaceAggregation": "sum"
}
],
"groupBy": [
{
"name": "service.name"
}
],
"bucketOptions": {
"kind": "log",
"spec": {
"scale": 4
}
}
}
},
{
"type": "builder_query",
"spec": {
"name": "B",
"signal": "metrics",
"disabled": true,
"stepInterval": 60,
"aggregations": [
{
"metricName": "http.server.request.count",
"timeAggregation": "increase",
"spaceAggregation": "sum"
}
],
"groupBy": [
{
"name": "service.name"
}
],
"bucketOptions": {
"kind": "linear",
"spec": {
"maxValue": 1000,
"numBuckets": 20
}
}
}
},
{
"type": "builder_formula",
"spec": {
"name": "F1",
"expression": "A / B",
"bucketOptions": {
"kind": "log",
"spec": {
"scale": 2
}
}
}
}
]
}
}
}
}
]
}
}
},
"links": [],
"layouts": [
{
"kind": "Grid",
"spec": {
"items": [
{
"x": 0,
"y": 0,
"width": 12,
"height": 8,
"content": {
"$ref": "#/spec/panels/p1"
}
}
]
}
}
]
}