Compare commits

...

3 Commits

Author SHA1 Message Date
Abhi Kumar
f2cef094a1 feat(dashboards): allow formulas and trace operators in top list panels 2026-10-08 23:08:51 +05:30
Abhi Kumar
5a02fb10fd chore(openapi): regenerate specs 2026-10-08 17:55:19 +05:30
Abhi Kumar
51bb0d0617 feat(dashboards): add plugin schema for top list panel 2026-10-08 17:55:05 +05:30
4 changed files with 93 additions and 2 deletions

View File

@@ -4178,6 +4178,7 @@ components:
signoz/TablePanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTablePanelSpec'
signoz/TextPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTextPanelSpec'
signoz/TimeSeriesPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTimeSeriesPanelSpec'
signoz/TopListPanel: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTopListPanelSpec'
propertyName: kind
oneOf:
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTimeSeriesPanelSpec'
@@ -4191,6 +4192,7 @@ components:
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTextPanelSpec'
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHeatmapPanelSpec'
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesScatterPlotPanelSpec'
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTopListPanelSpec'
type: object
DashboardtypesPanelPluginKind:
enum:
@@ -4205,6 +4207,7 @@ components:
- signoz/TextPanel
- signoz/HeatmapPanel
- signoz/ScatterPlotPanel
- signoz/TopListPanel
type: string
DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesAreaChartPanelSpec:
properties:
@@ -4338,6 +4341,18 @@ components:
- kind
- spec
type: object
DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTopListPanelSpec:
properties:
kind:
enum:
- signoz/TopListPanel
type: string
spec:
$ref: '#/components/schemas/DashboardtypesTopListPanelSpec'
required:
- kind
- spec
type: object
DashboardtypesPanelSpec:
properties:
display:
@@ -4829,6 +4844,18 @@ components:
timePreference:
$ref: '#/components/schemas/DashboardtypesTimePreference'
type: object
DashboardtypesTopListPanelSpec:
properties:
formatting:
$ref: '#/components/schemas/DashboardtypesPanelFormatting'
thresholds:
items:
$ref: '#/components/schemas/DashboardtypesComparisonThreshold'
nullable: true
type: array
visualization:
$ref: '#/components/schemas/DashboardtypesBasicVisualization'
type: object
DashboardtypesUpdatableDashboardV2:
properties:
image:

View File

@@ -2147,6 +2147,52 @@ func TestNumberPanelDefaults(t *testing.T) {
assert.Contains(t, outputStr, `"operator":"above"`, "expected stored/response JSON to contain operator:above")
}
func TestTopListPanelSpec(t *testing.T) {
data := []byte(`{
"variables": [],
"panels": {
"p1": {
"kind": "Panel",
"spec": {
"links": [],
"plugin": {
"kind": "signoz/TopListPanel",
"spec": {
"formatting": {"unit": "ms", "decimalPrecision": 2},
"thresholds": [{"value": 500, "operator": "above", "format": "background", "color": "Red"}]
}
},
"queries": [{"kind": "scalar", "spec": {"plugin": {"kind": "signoz/ClickHouseSQL", "spec": {"name": "A", "query": "SELECT 1"}}}}]
}
}
},
"links": [],
"layouts": []
}`)
d, err := unmarshalDashboard(data)
require.NoError(t, err, "unmarshal and validate failed")
require.IsType(t, &TopListPanelSpec{}, d.Panels["p1"].Spec.Plugin.Spec)
spec := d.Panels["p1"].Spec.Plugin.Spec.(*TopListPanelSpec)
assert.Equal(t, "ms", spec.Formatting.Unit)
require.Len(t, spec.Thresholds, 1)
assert.Equal(t, "background", spec.Thresholds[0].Format.ValueOrDefault())
output, err := json.Marshal(d)
require.NoError(t, err, "marshal dashboard failed")
roundTripped, err := unmarshalDashboard(output)
require.NoError(t, err, "round trip failed")
require.IsType(t, &TopListPanelSpec{}, roundTripped.Panels["p1"].Spec.Plugin.Spec)
roundTrippedSpec := roundTripped.Panels["p1"].Spec.Plugin.Spec.(*TopListPanelSpec)
assert.Equal(t, spec.Formatting, roundTrippedSpec.Formatting)
assert.Equal(t, spec.Thresholds, roundTrippedSpec.Thresholds)
t.Run("UnknownField_Rejected", func(t *testing.T) {
_, err := unmarshalDashboard([]byte(strings.Replace(string(data), `"formatting"`, `"legend": {}, "formatting"`, 1)))
assert.Error(t, err)
})
}
// TestPersesFixtureStorageRoundTrip exercises the typed → map[string]any →
// typed cycle that the create/get path performs against the kitchen-sink
// fixture. Catches plugin specs whose UnmarshalJSON expects a different shape
@@ -2431,7 +2477,7 @@ func TestPanelTypeQueryTypeCompatibility(t *testing.T) {
switch panelKind {
case "signoz/ListPanel":
return "raw"
case "signoz/TablePanel", "signoz/NumberPanel", "signoz/PieChartPanel", "signoz/HistogramPanel":
case "signoz/TablePanel", "signoz/NumberPanel", "signoz/PieChartPanel", "signoz/HistogramPanel", "signoz/TopListPanel":
return "scalar"
default:
return "time_series"
@@ -2482,6 +2528,13 @@ func TestPanelTypeQueryTypeCompatibility(t *testing.T) {
// Composite sub-queries
{"Table+Composite(promql)", mkComposite("signoz/TablePanel", "promql", `{"name":"A","query":"up"}`), true},
{"Table+Composite(clickhouse)", mkComposite("signoz/TablePanel", "clickhouse_sql", `{"name":"A","query":"SELECT 1"}`), false},
{"TopList+ClickHouse", mkQuery("signoz/TopListPanel", "signoz/ClickHouseSQL", `{"name":"A","query":"SELECT 1"}`), false},
{"TopList+PromQL", mkQuery("signoz/TopListPanel", "signoz/PromQLQuery", `{"name":"A","query":"up"}`), true},
{"TopList+Formula", mkQuery("signoz/TopListPanel", "signoz/Formula", `{"name":"F1","expression":"A+B"}`), false},
{"TopList+TraceOperator", mkQuery("signoz/TopListPanel", "signoz/TraceOperator", `{"name":"T1","expression":"A => B"}`), false},
{"TopList+Composite(clickhouse)", mkComposite("signoz/TopListPanel", "clickhouse_sql", `{"name":"A","query":"SELECT 1"}`), false},
{"TopList+Composite(formula)", mkComposite("signoz/TopListPanel", "builder_formula", `{"name":"F1","expression":"A+B"}`), false},
{"TopList+Composite(promql)", mkComposite("signoz/TopListPanel", "promql", `{"name":"A","query":"up"}`), true},
}
for _, tc := range cases {

View File

@@ -39,6 +39,7 @@ func (PanelPlugin) PrepareJSONSchema(s *jsonschema.Schema) error {
string(PanelKindText): schemaRef("DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTextPanelSpec"),
string(PanelKindHeatmap): schemaRef("DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHeatmapPanelSpec"),
string(PanelKindScatterPlot): schemaRef("DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesScatterPlotPanelSpec"),
string(PanelKindTopList): schemaRef("DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTopListPanelSpec"),
})
}
@@ -73,6 +74,7 @@ func (PanelPlugin) JSONSchemaOneOf() []any {
PanelPluginVariant[TextPanelSpec]{Kind: string(PanelKindText)},
PanelPluginVariant[HeatmapPanelSpec]{Kind: string(PanelKindHeatmap)},
PanelPluginVariant[ScatterPlotPanelSpec]{Kind: string(PanelKindScatterPlot)},
PanelPluginVariant[TopListPanelSpec]{Kind: string(PanelKindTopList)},
}
}
@@ -240,6 +242,7 @@ var (
PanelKindText: func() any { return new(TextPanelSpec) },
PanelKindHeatmap: func() any { return new(HeatmapPanelSpec) },
PanelKindScatterPlot: func() any { return new(ScatterPlotPanelSpec) },
PanelKindTopList: func() any { return new(TopListPanelSpec) },
}
queryPluginSpecs = map[QueryPluginKind]func() any{
QueryKindBuilder: func() any { return new(BuilderQuerySpec) },
@@ -266,6 +269,7 @@ var (
PanelKindText: {},
PanelKindHeatmap: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindPromQL, QueryKindClickHouseSQL},
PanelKindScatterPlot: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindPromQL, QueryKindClickHouseSQL},
PanelKindTopList: {QueryKindBuilder, QueryKindComposite, QueryKindFormula, QueryKindTraceOperator, QueryKindClickHouseSQL},
}
)

View File

@@ -178,10 +178,11 @@ const (
PanelKindText PanelPluginKind = "signoz/TextPanel"
PanelKindHeatmap PanelPluginKind = "signoz/HeatmapPanel"
PanelKindScatterPlot PanelPluginKind = "signoz/ScatterPlotPanel"
PanelKindTopList PanelPluginKind = "signoz/TopListPanel"
)
func (PanelPluginKind) Enum() []any {
return []any{PanelKindTimeSeries, PanelKindBarChart, PanelKindAreaChart, PanelKindNumber, PanelKindPieChart, PanelKindTable, PanelKindHistogram, PanelKindList, PanelKindText, PanelKindHeatmap, PanelKindScatterPlot}
return []any{PanelKindTimeSeries, PanelKindBarChart, PanelKindAreaChart, PanelKindNumber, PanelKindPieChart, PanelKindTable, PanelKindHistogram, PanelKindList, PanelKindText, PanelKindHeatmap, PanelKindScatterPlot, PanelKindTopList}
}
func (k PanelPluginKind) rendersWithoutQuery() bool {
@@ -249,6 +250,12 @@ type PieChartPanelSpec struct {
Legend Legend `json:"legend"`
}
type TopListPanelSpec struct {
Visualization BasicVisualization `json:"visualization"`
Formatting PanelFormatting `json:"formatting"`
Thresholds []ComparisonThreshold `json:"thresholds" validate:"dive"`
}
type TablePanelSpec struct {
Visualization BasicVisualization `json:"visualization"`
Formatting TableFormatting `json:"formatting"`