Compare commits

...

44 Commits

Author SHA1 Message Date
srikanthccv
09bba8960d chore(dashboards): describe the listed image the same way as the postable one
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-26 16:30:45 +05:30
srikanthccv
b8eec3257e chore(dashboards): review follow-ups on list rows, view names and plugin specs
Dashboard view names carry their 64 character limit in the schema. The
list row's source no longer lists system, which the list never returns,
and legacy says only what the caller can do with such a row. Name
replaces title where the JSON key is name. The per-kind plugin spec
descriptions are dropped: they restated the field names of the
referenced spec schema and would drift from it; the referenced schema
is the source of truth.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-26 16:27:01 +05:30
srikanthccv
30f35affaa chore(openapi): test every request example and describe the preview statements
Add the same decode-and-validate test the rule and dashboard examples
have to the notification channel, saved view and treemap examples, so a
renamed field or new required value cannot leave a published payload
invalid. Say that the preview returns statements only when verbose is
true, one per PromQL selector, one for the other query kinds and none
for formulas.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:44:47 +05:30
srikanthccv
74073261ea chore(dashboards): describe the contract, not the computation
Fields the UI stores but does not apply now say just that, without
naming the picker, control or menu involved. bucketCount is an
approximate target without the width selection algorithm. A panel is
shown only where a layout item references it, without the invisible
case spelled out. The builder query spec note states that its shape
depends on signal rather than how it is dispatched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
1257ce6383 chore(dashboards): thresholds target value columns; tighten bucket and all-value wording
The table renderer colors only value columns, so columnName cannot name
a group-by column. customAllValue no longer claims the UI substitutes
every value, which is true for query and custom variables but not for
dynamic ones, which send __all__. bucketWidth overrides bucketCount only
when positive, and the chart picks the first predefined width above the
range divided by the count rather than the nearest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
dc235febb8 chore(dashboards): leave temporality unset in the metrics example
The backend resolves temporality from the metric metadata when the
field is omitted. Pinning cumulative would silently return no data for
users who export delta temporality.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
fc54befbab chore(dashboards): describe what the UI actually honors
Review follow-up. Table column units and thresholds are keyed by the
column key (A, or A.count() with several aggregations), not by the
aggregation alias; the table example now uses the key. List filter
timestamps are RFC3339 strings. customAllValue, capturingRegexp, the
text variable constant flag, spec.duration, spec.refreshInterval,
dashboard links and link tooltips are stored but not read by the
current UI, and the descriptions now say so instead of promising the
behavior. Query variables offer every column of every row. bucketWidth
takes precedence over the approximate bucketCount. The patch example
counts errored spans rather than claiming a rate.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
38d5ac9a28 chore(dashboards): per-kind plugin spec descriptions, CSS colors and sharper examples
Each panel, query and variable plugin variant now describes its spec by
kind (the options a number panel takes, what a formula holds) instead of
the generic "kind-specific" sentence. Threshold colors are CSS colors,
with the SigNoz red preset hex as the example since the named color red
is a different shade. Threshold units say they are converted against
the panel unit. Tag descriptions carry an example, the public dashboard
time range names its accepted units, and the image field says only the
path prefix is validated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
f0e64800ab chore(dashboards): spell out query kinds, layout rules, units and filter grammar
Follow-up to review. Name the query plugin kinds each panel kind
rejects, explain that several named queries live inside one
signoz/CompositeQuery entry, list the enforced grid rules, say a panel
is visible only through a layout item, distinguish percent from
percentunit, give the list filter grammar an example, and state when
fillLessThan is required and its duration format.

Encode the constraints the validators enforce in the schema itself:
schemaVersion matches ^v6$, tags carry maxItems 10 and display names
maxLength 128. An enum for schemaVersion would have turned the generated
TypeScript type into an enum and broken callers that pass the literal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
0334bda0c2 chore(dashboards): regenerate the spec and clients on the stacked base
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
a360d10739 chore(dashboards): correct the request type per panel kind
Histogram panels bin client-side from time_series data and heatmap
panels request heatmap; the description on Query.kind recommended
distribution for both, which GetPanelQuery would forward unchanged.
Mirrors panelTypeToRequestType in the frontend.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
07cc947b63 chore(dashboards): describe dashboard v2 payload fields and add request examples
The dashboard v2 schemas carried no field descriptions, so every spec
consumer (generated clients, the MCP server's tool schemas, API docs)
saw structure without meaning. Add a description tag to every
JSON-visible field in dashboardtypes: the postable, updatable and
patchable shapes, the spec, panels, queries, variables, layouts, the
panel plugin option structs, and the list, view and public dashboard
types.

Attach request examples to CreateDashboardV2 (a variable-driven
service overview and a logs dashboard with a text and a table panel),
UpdateDashboardV2 (the full replacement shape) and PatchDashboardV2
(rename, and the two operations that add and place a panel). A test
decodes every example through the same validation the handlers run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
c7a698667a chore(metricsexplorer): describe the contract, not the computation
Drop table names, bucket schedules and retention mechanics from the
count descriptions; keep the caveats a caller acts on: over retained
data, approximate at the window edges, shortlisted by series count.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
3ee125fa12 chore(metricsexplorer): note the signoz name exclusion and hourly active series buckets
Stats and both treemaps always add NOT startsWith(metric_name, 'signoz'),
so an omitted filter does not mean every metric; the list endpoint does
not apply that exclusion. activeTimeSeries counts distinct fingerprints
in the hourly time_series_v4 metadata table with unix_milli at or after
now minus 120 minutes, so a series whose last hourly bucket started
before the cutoff is missed even if it reported inside the window.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
f460f7e152 chore(metricsexplorer): describe retention, bucketing and series caps behind the counts
Highlights sum the retained 30-minute aggregate and weekly metadata
tables, so they shrink with retention rather than counting everything
ever seen. Time series counts come from metadata buckets whose start is
rounded down to the table granularity. Inspection returns at most 50
series whether or not a filter is given. Metadata updates require delta
or cumulative for sums and histograms and normalize gauges and
summaries. The samples treemap ranks within the metrics with the most
time series.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
d48d78d875 chore(metricsexplorer): describe explorer payload fields and add a treemap example
The metrics explorer request, query parameter and response types carried
no field descriptions, so every consumer of docs/api/openapi.yml, from the
generated frontend clients to the MCP server's generated schemas, saw
names and types with no guidance on units, bounds, defaults or what each
count means. Describe every JSON and query field in metricsexplorertypes
from what the module code enforces, and attach one request example per
treemap mode to the GetMetricsTreemap operation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
5177cfaa97 chore(saved-views): describe the contract, not the computation
display.color says where the color shows and where it does not, without
naming the legacy picker mechanics. selectedFields states what an empty
array yields per signal instead of describing how each UI treats it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
9a6b634648 chore(saved-views): describe display.color and empty selectedFields as the UI treats them
display.color reaches only the legacy explorer view picker, which shows
it as the view's dot via the v1 extraData mapping; the saved views panel
never reads it. An empty selectedFields array is not "no preference"
everywhere: the saved views panel restores it as zero columns for
traces and as only the required columns for logs, while the legacy
picker falls back to the defaults. The description now tells callers
to send explicit columns for list views.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
ec31df0007 chore(saved-views): describe saved view payload fields and add request examples
The saved view schemas carried no field descriptions, so every spec
consumer (generated clients, API docs, the MCP server's tool schemas)
saw names and types without format, accepted values or inter-field
rules such as name versus generateName. Describe every wire field of
the postable, updatable, stored and listed shapes and attach request
examples for each explorer source to the create and update endpoints.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
42e1920a0f chore(alertmanager): describe the contract, not the computation
Drop receiver and notifier mechanics from channel descriptions: say that
rules and routing policies reference displayName rather than how the
receiver is built, that Jira customFields are stored but not in the
issue Jira receives, and name the v1 channel kind, config and routed
channels without the alertmanager internals.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
a203144ec9 chore(alertmanager): mark Jira customFields unused and say when secrets are returned
The Jira notifier builds the issue payload from title, body, labels,
assignee and priority and never reads customFields, so the description
now says the field is stored but not sent. The v2 create and update
handlers return the gettable channel with its config, so secrets come
back from create, update and fetch by ID; only the list omits them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
3a81aa70e9 chore(alertmanager): regenerate the spec and clients on the stacked base
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
b8ece63e4d chore(alertmanager): say rules reference displayName in channel descriptions
ToReceiver builds the alertmanager receiver from DisplayName, so alert
rule channels and routing policies resolve a channel by its display
name. The descriptions on name and displayName claimed the opposite.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
64c80282a6 chore(alertmanager): describe notification channel payload fields and add request examples
The notification channel schemas carried names, types and required flags
but no field descriptions, so every spec consumer (generated clients,
docs, the MCP server's tool schemas) saw structure with no guidance on
formats, defaults or which fields exclude each other. Add a description
tag to every JSON-visible field of the channel payloads, the ten provider
specs, the list parameters, the repair response and the deprecated alerts
response, and attach request examples to the channel create, update and
test endpoints: one per provider kind for create.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:36 +05:30
srikanthccv
3680b69cf6 chore(ruler): describe the contract, not the computation
newGroupEvalDelay: drop the metadata first-seen mechanism; keep which
rules it applies to and that unknown groups evaluate immediately.
alertCount: drop the SendUnmatched rationale; keep that every evaluated
series counts.
GettableRules.rules: drop "stored".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
bc1ac14a10 chore(ruler): say which signals newGroupEvalDelay applies to
The grace period is applied by FilterNewSeries only for metric queries:
builder queries over logs or traces return before any filtering, and
new is measured from the latest first-seen time of the series' group-by
attribute values in the metric metadata. A series whose values have no
metadata is evaluated at once. The description promised a general grace
period for any newly seen group.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
9af598ba9b chore(ruler): correct test alert count, label suggestions, legacy codes and channel references
alertCount on a test notification counts every evaluated series, since
the test path enables SendUnmatched, not the alerts that would have
fired. The labels on the list response are organization-wide
suggestions capped at 1000 pairs, not pairs from the matching rules.
The legacy op and matchType codes are JSON strings ('1' to '7', '1' to
'5'); a bare number fails decoding. Threshold channels and
preferredChannels name a notification channel's displayName, which is
what ToReceiver turns into the receiver name.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
46725d9f6b chore(ruler): describe the alert rule payload fields in the OpenAPI spec
The rule types carried no field descriptions, so every consumer of
docs/api/openapi.yml (the generated frontend client, SDKs, and the MCP
server's generated input schemas) saw names and types with no guidance
on formats, units, accepted values, defaults or which fields apply to
schemaVersion v1 versus v2alpha1. Add a one line description tag to
every JSON visible field of PostableRule, RuleCondition,
AlertCompositeQuery, the threshold and evaluation envelopes,
NotificationSettings, Renotify, the rule read models and the v3 list
parameters, so the teaching is authored once upstream.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
04088f970c chore(querier): describe the contract, not the computation
Drop mechanism language from the field descriptions: evaluation order,
execution paths, field discovery, cache buckets, statement counts and
rendering hints. Keep every caveat a caller acts on in its shortest
form: unreliable formula chaining, accepted but ignored fields, results
excluded while the query may still run, cost-only execution stats.
Request-side aggregation alias now states the __result_N fallback the
response carries. Remove joins from the request summary; they are not
part of the envelope.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
3217962140 chore(querier): keep the shared filter description signal-neutral
The Filter schema is referenced from traces, logs and metrics builder
queries, the trace operator, the metrics explorer and the infra
monitoring requests, so a traces example such as duration_nano showed
up on every one of them. Describe the grammar on the shared schema and
give each builder query variant a filter example from its own signal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
88e67248c5 chore(querier): describe execution stats as a cost indicator only
rowsScanned, bytesScanned and durationMs measure how much work the store
did, not whether any data matched: an empty result can still scan many
rows. Say so, so callers never read the stats as a sign that a service
emits telemetry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
dd11dde63d chore(querier): drop the dotted aggregation reference from the formula description
Bare A.0 and A.alias never worked: govaluate treats the dot as an
accessor and discovers no variable, so only the bracketed [A.0] form
reaches the evaluator. Tracked in SigNoz/pulse-pod#604; the description
teaches whole-query references only until that is settled.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
9e4a69d47d chore(querier): qualify variable, formula chaining and aggregation metadata claims
__all__ removes a condition only for dynamic variables in builder
filters and PromQL; query, custom and text variables, and ClickHouse
SQL for every type, substitute it literally. Builder filters accept
only $name and leave a quoted {{name}} alone, while PromQL and
ClickHouse SQL also take {{name}}, [[name]] and {{.name}}. Formulas run
in map order, so referencing one formula from another is not reliable.
Aggregation buckets come back in no guaranteed order after cache
merging and an unnamed aggregation is aliased __result_N, so readers
should match by index. Trace operator offset applies to raw requests
only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
7a55a92af1 chore(querier): say which query builder fields execution actually honors
disabled only filters the result out of the response; every query kind
still runs (postprocess filterDisabledQueries). secondaryAggregations
are never read outside the types package. Formula having is never
applied. The trace operator filter only widens key discovery, its
cursor is never read, and raw requests default to 100 rows when limit
is 0. selectFields names the always-present columns per signal, and
durationMs is the sum of statement durations, not request latency.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
b5f2f5d9b5 chore(querier): regenerate the spec and clients on the stacked base
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
2df0b25d5b chore(querier): correct fillGaps, limit, limitBy and histogram comparison descriptions
fillGaps zero-fills every builder query and formula regardless of
aggregation; only PromQL and ClickHouse SQL queries are skipped. A raw
request ordered by timestamp with limit 0 returns a 10000-row page, not
every row. limitBy is stored but never consumed by execution. The
comparison space aggregation parameter estimates histogram observations
above or below a bucket bound, not series meeting a condition.

The PrepareJSONSchema hooks that retype value and signal rebuilt the
property schema from scratch and discarded the struct tag description,
so those four descriptions never reached the spec; carry it over.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
f7007c91f6 chore(querier): describe the query builder v5 request and response fields in the OpenAPI spec
The v5 query_range payload carried no field descriptions, so every spec
consumer, the generated frontend clients, the MCP server's tool schemas
and the API reference, saw names and types but no units, formats,
accepted values or inter-field rules. Add one-line description tags to
every wire-visible field of the request types (envelope, builder query,
aggregations, filter, having, order, functions, limits, buckets,
formula, PromQL, ClickHouse SQL, trace operator, variables and format
options) and of the response types (time series, scalar, raw, preview,
execution stats and warnings). Trailing comments that only restated a
field moved into the tag.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
ee75d99b9a chore(telemetrytypes): describe the contract, not the computation
Drop table and column names, SQL operator mechanics and code-path
language from the lookup descriptions. Keep every caller-visible
caveat: the 6 hour start rounding, which lookups apply which bound and
at what granularity, the meter source's case-sensitive wildcard match,
and the per-signal limit that intrinsic fields can exceed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
1970d3d7b5 chore(telemetrytypes): broaden the field name example beyond traces
TelemetryFieldKey is shared by every signal and by the infra and quick
filter endpoints, so the example names now include an infrastructure
attribute and say the available names depend on the ingested data.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
05ca41615f chore(telemetrytypes): describe lookup bounds and meter search per path
The API rounds startUnixMilli down to a 6 hour boundary before any
lookup (NewFieldKeySelectorFromPostableFieldKeysParams), so the earlier
removal of that claim was wrong. Bounds now read per path: key lookups
and meter lookups ignore both; trace and log values keep the rounded
start's day on with no upper bound; metric attribute values use last and
first reported times; relatedValues apply both bounds on the related
metadata table. The meter source searches keys and values with a
case-sensitive LIKE whose % and _ are unescaped wildcards, so the
case-insensitive substring wording is now qualified for it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
ce27b29aa1 chore(telemetrytypes): describe the time bounds, filters and limits the lookups actually apply
Key lookups ignore startUnixMilli and endUnixMilli entirely (the span
keys filter is commented out, and the log and metric key queries carry
no time predicate). Trace and log value lookups apply only a day-level
lower bound through tagTableSinceDay; metric value lookups bound by
last_reported and first_reported instead. fieldContext and
fieldDataType are not applied to metric attribute keys. limit is read
per signal: intrinsic and materialized fields are added on top, and an
unspecified signal queries traces, logs and metrics with the same limit
each. The exact selector match type compares case-sensitively in the
key tables, and the API always sends fuzzy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
6c4562eb76 chore(telemetrytypes): describe the field key and value selector fields in the OpenAPI spec
The field keys and field values endpoints advertised their query
parameters and response shapes without a single description, so every
spec consumer (generated clients, the MCP server's tool schemas, API
docs) saw names and types but nothing about defaults, units, or how
searchText and existingQuery are interpreted. Add a one-line description
to each JSON- and query-visible field, derived from the selector
builders and the metadata store.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
6e42ada4dd chore(metrictypes): call the comparison result an estimate
The histogram comparison count is interpolated between bucket bounds, so
the descriptions now name it an estimated count rather than a count.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
srikanthccv
5088dbeb7d chore(metrictypes): describe the metric aggregation parameter fields in the OpenAPI spec
ComparisonSpaceAggregationParam is the only metrictypes struct the API
exposes (via builder query metric aggregations), and its operator and
threshold fields carried no description. Document the two accepted
operators and what the threshold means, as implemented by the histogram
count statement builder.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 15:13:35 +05:30
61 changed files with 6682 additions and 1361 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -140,6 +140,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
Description: "This endpoint creates a notification channel",
Request: new(alertmanagertypes.PostableNotificationChannel),
RequestContentType: "application/json",
RequestExamples: createNotificationChannelExamples(),
Response: new(alertmanagertypes.GettableNotificationChannel),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
@@ -224,6 +225,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
Description: "This endpoint replaces a notification channel's configuration in full. Neither name is part of the request body: both are immutable. The kind may change, which replaces the channel's notifier configuration.",
Request: new(alertmanagertypes.UpdatableNotificationChannel),
RequestContentType: "application/json",
RequestExamples: updateNotificationChannelExamples(),
Response: new(alertmanagertypes.GettableNotificationChannel),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
@@ -309,6 +311,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
Description: "This endpoint sends a test notification for the configuration in the request body. The channel need not exist and nothing is persisted, so the body carries a configuration only.",
Request: new(alertmanagertypes.TestableNotificationChannel),
RequestContentType: "application/json",
RequestExamples: testNotificationChannelExamples(),
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,

View File

@@ -0,0 +1,201 @@
package signozapiserver
import "github.com/SigNoz/signoz/pkg/http/handler"
// channelConfig wraps one provider spec in the {kind, spec} envelope every
// channel write carries.
func channelConfig(kind string, spec map[string]any) map[string]any {
return map[string]any{"kind": kind, "spec": spec}
}
func slackChannelSpec() map[string]any {
return map[string]any{
"apiUrl": "https://hooks.slack.com/services/T000/B000/XXXXXXXX",
"channel": "#alerts-platform",
"sendResolved": true,
}
}
func pagerdutyChannelSpec() map[string]any {
return map[string]any{
"routingKey": "R0000000000000000000000000000000",
"severity": "critical",
}
}
func webhookChannelSpec() map[string]any {
return map[string]any{
"url": "https://alerts.example.com/signoz",
"bearerToken": "00000000-0000-0000-0000-000000000000",
}
}
// createNotificationChannelExamples returns one create payload per channel
// kind. Each carries the kind's required fields plus one or two optional ones
// that show a default being overridden.
func createNotificationChannelExamples() []handler.OpenAPIExample {
return []handler.OpenAPIExample{
{
Name: "slack",
Summary: "Slack incoming webhook",
Description: "Posts to a Slack channel through an incoming webhook. `sendResolved` is off by default for Slack, so it is turned on here.",
Value: map[string]any{
"name": "slack-platform",
"displayName": "Slack platform",
"config": channelConfig("slack", slackChannelSpec()),
},
},
{
Name: "email",
Summary: "Email recipients",
Description: "Sends to a comma-separated recipient list. SMTP transport comes from the deployment's global config, so only recipients and body are configurable.",
Value: map[string]any{
"name": "email-oncall",
"displayName": "Email on-call",
"config": channelConfig("email", map[string]any{
"to": "oncall@example.com, platform@example.com",
"headers": map[string]any{"Subject": "[SigNoz] {{ .CommonLabels.alertname }}"},
}),
},
},
{
Name: "webhook_bearer",
Summary: "Webhook with a bearer token",
Description: "Delivers the Alertmanager webhook payload to an HTTP endpoint. Use either `bearerToken` or `username`/`password`, never both.",
Value: map[string]any{
"name": "webhook-incident-bot",
"displayName": "Incident bot webhook",
"config": channelConfig("webhook", webhookChannelSpec()),
},
},
{
Name: "pagerduty",
Summary: "PagerDuty Events API v2",
Description: "Creates PagerDuty incidents through an Events API v2 integration key.",
Value: map[string]any{
"name": "pagerduty-oncall",
"displayName": "PagerDuty on-call",
"config": channelConfig("pagerduty", pagerdutyChannelSpec()),
},
},
{
Name: "opsgenie",
Summary: "Opsgenie API integration",
Description: "Creates Opsgenie alerts through an API integration key, pinned to priority P2.",
Value: map[string]any{
"name": "opsgenie-sre",
"displayName": "Opsgenie SRE",
"config": channelConfig("opsgenie", map[string]any{
"apiKey": "00000000-0000-0000-0000-000000000000",
"priority": "P2",
}),
},
},
{
Name: "msteams",
Summary: "Microsoft Teams incoming webhook",
Description: "Posts an adaptive card to a Teams channel through an incoming webhook.",
Value: map[string]any{
"name": "msteams-platform",
"displayName": "Teams platform",
"config": channelConfig("msteams", map[string]any{
"webhookUrl": "https://example.webhook.office.com/webhookb2/00000000-0000-0000-0000-000000000000",
}),
},
},
{
Name: "googlechat",
Summary: "Google Chat space webhook",
Description: "Posts to a Google Chat space through its webhook URL.",
Value: map[string]any{
"name": "googlechat-platform",
"displayName": "Google Chat platform",
"config": channelConfig("googlechat", map[string]any{
"webhookUrl": "https://chat.googleapis.com/v1/spaces/AAAA0000000/messages?key=XXXX&token=XXXX",
}),
},
},
{
Name: "jira",
Summary: "Jira Cloud issues",
Description: "Creates one Jira Cloud issue per alert group and reopens it if the alert fires again within `reopenDuration`. Authenticates with an Atlassian account email and API token.",
Value: map[string]any{
"name": "jira-platform",
"displayName": "Jira platform",
"config": channelConfig("jira", map[string]any{
"site": "https://example.atlassian.net",
"project": "OPS",
"issueType": "Bug",
"email": "oncall@example.com",
"apiToken": "XXXXXXXXXXXXXXXXXXXXXXXX",
"labels": []any{"signoz", "alert"},
"reopenDuration": "3d",
}),
},
},
{
Name: "jsmops",
Summary: "Jira Service Management Ops",
Description: "Creates JSM Ops alerts through an integration API key. JSM Ops speaks the Opsgenie alert API, so `priority` takes P1 to P5.",
Value: map[string]any{
"name": "jsmops-oncall",
"displayName": "JSM Ops on-call",
"config": channelConfig("jsmops", map[string]any{
"apiKey": "00000000-0000-0000-0000-000000000000",
"priority": "P1",
"tags": "signoz,platform",
}),
},
},
{
Name: "incidentio",
Summary: "incident.io HTTP alert source",
Description: "Sends alert events to an incident.io HTTP alert source. `url` and `token` are copied from the source's setup page; the token goes without the Bearer prefix.",
Value: map[string]any{
"name": "incidentio-platform",
"displayName": "incident.io platform",
"config": channelConfig("incidentio", map[string]any{
"url": "https://api.incident.io/v2/alert_events/http/00000000-0000-0000-0000-000000000000",
"token": "XXXXXXXXXXXXXXXXXXXXXXXX",
"metadata": map[string]any{"team": "platform"},
}),
},
},
}
}
// updateNotificationChannelExamples shows that an update carries only config:
// name and displayName are immutable, and the kind may change.
func updateNotificationChannelExamples() []handler.OpenAPIExample {
return []handler.OpenAPIExample{
{
Name: "slack_rotate_webhook",
Summary: "Replace a Slack webhook URL",
Description: "Replaces the stored configuration in full; every field not sent falls back to its default.",
Value: map[string]any{"config": channelConfig("slack", slackChannelSpec())},
},
{
Name: "switch_to_pagerduty",
Summary: "Change the kind to PagerDuty",
Description: "The kind may change on update, replacing the channel's notifier configuration while its name and references stay.",
Value: map[string]any{"config": channelConfig("pagerduty", pagerdutyChannelSpec())},
},
}
}
// testNotificationChannelExamples sends one test notification; the body is a
// configuration only because nothing is persisted.
func testNotificationChannelExamples() []handler.OpenAPIExample {
return []handler.OpenAPIExample{
{
Name: "slack",
Summary: "Test a Slack webhook",
Value: map[string]any{"config": channelConfig("slack", slackChannelSpec())},
},
{
Name: "webhook",
Summary: "Test a webhook endpoint",
Value: map[string]any{"config": channelConfig("webhook", webhookChannelSpec())},
},
}
}

View File

@@ -0,0 +1,41 @@
package signozapiserver
import (
"encoding/json"
"testing"
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
"github.com/stretchr/testify/require"
)
// The examples are advertised in the OpenAPI spec as payloads the API accepts,
// so each must decode through the same validation the handlers run.
func TestNotificationChannelExamplesAreValidPayloads(t *testing.T) {
for _, ex := range createNotificationChannelExamples() {
t.Run("create/"+ex.Name, func(t *testing.T) {
raw, err := json.Marshal(ex.Value)
require.NoError(t, err)
var postable alertmanagertypes.PostableNotificationChannel
require.NoError(t, json.Unmarshal(raw, &postable))
require.NoError(t, postable.Validate())
})
}
for _, ex := range updateNotificationChannelExamples() {
t.Run("update/"+ex.Name, func(t *testing.T) {
raw, err := json.Marshal(ex.Value)
require.NoError(t, err)
var updatable alertmanagertypes.UpdatableNotificationChannel
require.NoError(t, json.Unmarshal(raw, &updatable))
require.NoError(t, updatable.Validate())
})
}
for _, ex := range testNotificationChannelExamples() {
t.Run("test/"+ex.Name, func(t *testing.T) {
raw, err := json.Marshal(ex.Value)
require.NoError(t, err)
var testable alertmanagertypes.TestableNotificationChannel
require.NoError(t, json.Unmarshal(raw, &testable))
require.NoError(t, testable.Validate())
})
}
}

View File

@@ -77,6 +77,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
Description: "This endpoint creates a dashboard in the v2 format that follows Perses spec.",
Request: new(dashboardtypes.PostableDashboardV2),
RequestContentType: "application/json",
RequestExamples: createDashboardExamples(),
Response: new(dashboardtypes.GettableDashboardV2),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
@@ -200,6 +201,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
Description: "This endpoint updates a v2-shape dashboard's metadata, data, and tag set. Locked dashboards are rejected.",
Request: new(dashboardtypes.UpdatableDashboardV2),
RequestContentType: "application/json",
RequestExamples: updateDashboardExamples(),
Response: new(dashboardtypes.GettableDashboardV2),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
@@ -233,6 +235,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
// the array-of-ops schema visible to spec consumers; the runtime decoder
// parses JSON regardless of the request's actual Content-Type header.
RequestContentType: "application/json",
RequestExamples: patchDashboardExamples(),
Response: new(dashboardtypes.GettableDashboardV2),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,

View File

@@ -0,0 +1,268 @@
package signozapiserver
import "github.com/SigNoz/signoz/pkg/http/handler"
// gridItem places a panel on the 12-column grid of a Grid layout.
func gridItem(panelID string, x, y, width, height int) map[string]any {
return map[string]any{
"x": x, "y": y, "width": width, "height": height,
"content": map[string]any{"$ref": "#/spec/panels/" + panelID},
}
}
func gridLayout(items ...map[string]any) map[string]any {
list := make([]any, 0, len(items))
for _, item := range items {
list = append(list, item)
}
return map[string]any{"kind": "Grid", "spec": map[string]any{"items": list}}
}
// builderQuery wraps a builder query spec in the panel query envelope.
// requestType is the result shape the panel renders: time_series, scalar or raw.
func builderQuery(requestType string, spec map[string]any) map[string]any {
return map[string]any{
"kind": requestType,
"spec": map[string]any{
"name": spec["name"],
"plugin": map[string]any{"kind": "signoz/BuilderQuery", "spec": spec},
},
}
}
func panel(name, description string, plugin map[string]any, queries ...map[string]any) map[string]any {
list := make([]any, 0, len(queries))
for _, q := range queries {
list = append(list, q)
}
return map[string]any{
"kind": "Panel",
"spec": map[string]any{
"display": map[string]any{"name": name, "description": description},
"links": []any{},
"plugin": plugin,
"queries": list,
},
}
}
func serviceNameGroupBy() []any {
return []any{map[string]any{"name": "service.name", "fieldContext": "resource", "fieldDataType": "string"}}
}
// createDashboardExamples returns example payloads for the dashboard create
// endpoint. They cover a variable-driven service overview and a logs dashboard
// that pairs a query-less text panel with a table.
func createDashboardExamples() []handler.OpenAPIExample {
return []handler.OpenAPIExample{
{
Name: "service_overview",
Summary: "Service overview with a variable, a time series panel and a number panel",
Description: "A `$service` list variable fed by the observed `service.name` values filters both panels. The time series panel counts spans per service; the number panel renders a single scalar and turns red above 100 errors. `generateName` lets the server derive the name from the display name.",
Value: map[string]any{
"schemaVersion": "v6",
"generateName": true,
"tags": []any{map[string]any{"key": "team", "value": "platform"}},
"spec": map[string]any{
"display": map[string]any{"name": "Service Overview", "description": "Request rate and errors per service"},
"variables": []any{
map[string]any{
"kind": "ListVariable",
"spec": map[string]any{
"name": "service",
"display": map[string]any{"name": "Service"},
"allowMultiple": true,
"allowAllValue": true,
"sort": "alphabetical-asc",
"plugin": map[string]any{
"kind": "signoz/DynamicVariable",
"spec": map[string]any{"name": "service.name", "signal": "traces"},
},
},
},
},
"panels": map[string]any{
"request-rate": panel("Requests", "Spans per service",
map[string]any{
"kind": "signoz/TimeSeriesPanel",
"spec": map[string]any{
"formatting": map[string]any{"decimalPrecision": "0"},
"legend": map[string]any{"position": "bottom"},
},
},
builderQuery("time_series", map[string]any{
"name": "A",
"signal": "traces",
"aggregations": []any{map[string]any{"expression": "count()", "alias": "requests"}},
"filter": map[string]any{"expression": "service.name IN $service"},
"groupBy": serviceNameGroupBy(),
"legend": "{{service.name}}",
}),
),
"error-count": panel("Errors", "Spans with an error status",
map[string]any{
"kind": "signoz/NumberPanel",
"spec": map[string]any{
"thresholds": []any{
map[string]any{"value": 100, "operator": "above", "color": "#F1575F", "format": "background"},
},
},
},
builderQuery("scalar", map[string]any{
"name": "A",
"signal": "traces",
"aggregations": []any{map[string]any{"expression": "count()", "alias": "errors"}},
"filter": map[string]any{"expression": "has_error = true AND service.name IN $service"},
}),
),
},
"layouts": []any{gridLayout(
gridItem("request-rate", 0, 0, 8, 6),
gridItem("error-count", 8, 0, 4, 6),
)},
"duration": "1h",
"links": []any{},
},
},
},
{
Name: "logs_table_with_runbook",
Summary: "Logs dashboard with a text panel and a table panel",
Description: "A `signoz/TextPanel` renders markdown and must send `queries: []`. The table counts error logs per service as a scalar result, orders by the `errors` alias and colors cells above 1000 errors; the threshold names the column by its key `A` (the query name), not by the alias.",
Value: map[string]any{
"schemaVersion": "v6",
"name": "error-logs-by-service",
"tags": []any{},
"spec": map[string]any{
"display": map[string]any{"name": "Error Logs by Service", "description": ""},
"variables": []any{},
"panels": map[string]any{
"runbook": panel("Runbook", "What to do when errors spike",
map[string]any{
"kind": "signoz/TextPanel",
"spec": map[string]any{
"mode": "markdown",
"text": "## Error logs\n\n1. Find the service with the most errors in the table.\n2. Open its logs and group by `code.function`.",
"presentation": map[string]any{"textAlign": "left", "verticalAlign": "top"},
"headerOptions": map[string]any{"hide": false},
},
},
),
"errors-by-service": panel("Error logs per service", "",
map[string]any{
"kind": "signoz/TablePanel",
"spec": map[string]any{
"formatting": map[string]any{"decimalPrecision": "0"},
"thresholds": []any{
map[string]any{"columnName": "A", "value": 1000, "operator": "above", "color": "#F1575F", "format": "background"},
},
},
},
builderQuery("scalar", map[string]any{
"name": "A",
"signal": "logs",
"aggregations": []any{map[string]any{"expression": "count()", "alias": "errors"}},
"filter": map[string]any{"expression": "severity_text = 'ERROR'"},
"groupBy": serviceNameGroupBy(),
"order": []any{map[string]any{"key": map[string]any{"name": "errors"}, "direction": "desc"}},
"limit": 20,
}),
),
},
"layouts": []any{gridLayout(
gridItem("runbook", 0, 0, 4, 6),
gridItem("errors-by-service", 4, 0, 8, 6),
)},
"links": []any{},
},
},
},
}
}
// updateDashboardExamples returns the example payload for the dashboard update
// endpoint: the full replacement shape, including the immutable name.
func updateDashboardExamples() []handler.OpenAPIExample {
return []handler.OpenAPIExample{
{
Name: "replace_dashboard",
Summary: "Replace a dashboard with a single CPU time series panel",
Description: "Update is a full replacement: every field is written as sent, so fetch the dashboard first and resend the unchanged parts. `name` must equal the dashboard's current name.",
Value: map[string]any{
"schemaVersion": "v6",
"name": "host-cpu",
"tags": []any{map[string]any{"key": "team", "value": "infra"}},
"spec": map[string]any{
"display": map[string]any{"name": "Host CPU", "description": "CPU time by state"},
"variables": []any{},
"panels": map[string]any{
"cpu-time": panel("CPU time", "Rate of system.cpu.time by state",
map[string]any{
"kind": "signoz/TimeSeriesPanel",
"spec": map[string]any{
"chartAppearance": map[string]any{"fillMode": "gradient"},
"legend": map[string]any{"position": "right"},
},
},
builderQuery("time_series", map[string]any{
"name": "A",
"signal": "metrics",
"aggregations": []any{map[string]any{
"metricName": "system.cpu.time",
"timeAggregation": "rate",
"spaceAggregation": "sum",
}},
"groupBy": []any{map[string]any{"name": "state", "fieldContext": "attribute", "fieldDataType": "string"}},
"legend": "{{state}}",
}),
),
},
"layouts": []any{gridLayout(gridItem("cpu-time", 0, 0, 12, 7))},
"duration": "3h",
"refreshInterval": "30s",
"links": []any{},
},
},
},
}
}
// patchDashboardExamples returns example payloads for the dashboard patch
// endpoint. The request body is the RFC 6902 operation array itself.
func patchDashboardExamples() []handler.OpenAPIExample {
return []handler.OpenAPIExample{
{
Name: "rename",
Summary: "Rename a dashboard",
Description: "Replaces the display name without resending anything else.",
Value: []any{
map[string]any{"op": "replace", "path": "/spec/display/name", "value": "Service Overview (production)"},
},
},
{
Name: "add_panel",
Summary: "Add a panel and place it",
Description: "Adding a panel takes two operations: one adds the panel under `/spec/panels/<id>`, the other appends a grid item that references it to the first layout. A panel that no layout places is accepted but not shown.",
Value: []any{
map[string]any{
"op": "add",
"path": "/spec/panels/error-rate",
"value": panel("Error count", "Errored spans per time bucket",
map[string]any{"kind": "signoz/TimeSeriesPanel", "spec": map[string]any{}},
builderQuery("time_series", map[string]any{
"name": "A",
"signal": "traces",
"aggregations": []any{map[string]any{"expression": "count()"}},
"filter": map[string]any{"expression": "has_error = true"},
}),
),
},
map[string]any{
"op": "add",
"path": "/spec/layouts/0/spec/items/-",
"value": gridItem("error-rate", 0, 7, 12, 6),
},
},
},
}
}

View File

@@ -0,0 +1,51 @@
package signozapiserver
import (
"encoding/json"
"testing"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
// The examples are advertised in the OpenAPI spec as payloads the API accepts,
// so each must decode through the same validation the handlers run.
func TestDashboardExamplesAreValidPayloads(t *testing.T) {
for _, ex := range createDashboardExamples() {
t.Run("create/"+ex.Name, func(t *testing.T) {
raw, err := json.Marshal(ex.Value)
require.NoError(t, err)
var postable dashboardtypes.PostableDashboardV2
require.NoError(t, json.Unmarshal(raw, &postable))
})
}
var base dashboardtypes.UpdatableDashboardV2
for _, ex := range updateDashboardExamples() {
t.Run("update/"+ex.Name, func(t *testing.T) {
raw, err := json.Marshal(ex.Value)
require.NoError(t, err)
require.NoError(t, json.Unmarshal(raw, &base))
})
}
existing := &dashboardtypes.DashboardV2{
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
Source: dashboardtypes.SourceUser,
DashboardV2MetadataBase: base.DashboardV2MetadataBase,
Name: base.Name,
Spec: base.Spec,
}
for _, ex := range patchDashboardExamples() {
t.Run("patch/"+ex.Name, func(t *testing.T) {
raw, err := json.Marshal(ex.Value)
require.NoError(t, err)
var patch dashboardtypes.PatchableDashboardV2
require.NoError(t, json.Unmarshal(raw, &patch))
_, err = patch.Apply(existing)
require.NoError(t, err)
})
}
}

View File

@@ -70,6 +70,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
Description: "This endpoint returns a treemap visualization showing the proportional distribution of metrics by sample count or time series count",
Request: new(metricsexplorertypes.TreemapRequest),
RequestContentType: "application/json",
RequestExamples: treemapRequestExamples(),
Response: new(metricsexplorertypes.TreemapResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,

View File

@@ -0,0 +1,33 @@
package signozapiserver
import "github.com/SigNoz/signoz/pkg/http/handler"
// treemapRequestExamples returns example payloads for the metrics treemap
// endpoint, one per mode.
func treemapRequestExamples() []handler.OpenAPIExample {
return []handler.OpenAPIExample{
{
Name: "treemap_timeseries",
Summary: "Top metrics by time series count",
Description: "Sizes each metric by the number of distinct time series it produced in the time range, limited to the 50 largest. Only the `timeseries` array of the response is populated.",
Value: map[string]any{
"start": 1640995200000,
"end": 1640998800000,
"limit": 50,
"mode": "timeseries",
},
},
{
Name: "treemap_samples_filtered",
Summary: "Top metrics by sample count for one host",
Description: "Sizes each metric by the number of samples ingested, restricted with a query builder filter on a metric attribute. Only the `samples` array of the response is populated.",
Value: map[string]any{
"start": 1640995200000,
"end": 1640998800000,
"limit": 20,
"mode": "samples",
"filter": map[string]any{"expression": "host.name = 'web-1'"},
},
},
}
}

View File

@@ -0,0 +1,23 @@
package signozapiserver
import (
"encoding/json"
"testing"
"github.com/SigNoz/signoz/pkg/types/metricsexplorertypes"
"github.com/stretchr/testify/require"
)
// The examples are advertised in the OpenAPI spec as payloads the API accepts,
// so each must decode through the same validation the handler runs.
func TestTreemapExamplesAreValidPayloads(t *testing.T) {
for _, ex := range treemapRequestExamples() {
t.Run(ex.Name, func(t *testing.T) {
raw, err := json.Marshal(ex.Value)
require.NoError(t, err)
var req metricsexplorertypes.TreemapRequest
require.NoError(t, json.Unmarshal(raw, &req))
require.NoError(t, req.Validate())
})
}
}

View File

@@ -48,6 +48,7 @@ func (provider *provider) addSavedViewRoutes(router *mux.Router) error {
Description: "Persists a saved view for the explore page. Returns the id of the created view.",
Request: new(savedviewtypes.PostableSavedView),
RequestContentType: "application/json",
RequestExamples: postableSavedViewExamples(),
Response: new(types.Identifiable),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
@@ -102,6 +103,7 @@ func (provider *provider) addSavedViewRoutes(router *mux.Router) error {
Description: "Replaces a saved view's name and query.",
Request: new(savedviewtypes.UpdatableSavedView),
RequestContentType: "application/json",
RequestExamples: updatableSavedViewExamples(),
Response: nil,
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,

View File

@@ -0,0 +1,176 @@
package signozapiserver
import "github.com/SigNoz/signoz/pkg/http/handler"
// postableSavedViewExamples returns example payloads attached to the saved view
// create endpoint. They cover each explorer source and both list and graph panels.
func postableSavedViewExamples() []handler.OpenAPIExample {
return []handler.OpenAPIExample{
{
Name: "traces_list",
Summary: "Traces list view with filter and columns",
Description: "A list panel over spans slower than 500ms in one service, ordered newest first, with three explorer columns.",
Value: map[string]any{
"name": "slow-checkout-traces",
"source": "traces",
"schemaVersion": "v2",
"spec": map[string]any{
"displayName": "Slow checkout traces",
"panelType": "list",
"requestType": "raw",
"queries": []any{
map[string]any{
"type": "builder_query",
"spec": map[string]any{
"name": "A",
"signal": "traces",
"filter": map[string]any{"expression": "service.name = 'checkoutservice' AND duration_nano > 500000000"},
"order": []any{map[string]any{"key": map[string]any{"name": "timestamp"}, "direction": "desc"}},
"limit": 100,
},
},
},
"selectedFields": []any{
map[string]any{"name": "service.name", "signal": "traces", "fieldContext": "resource", "fieldDataType": "string"},
map[string]any{"name": "name", "signal": "traces", "fieldContext": "span", "fieldDataType": "string"},
map[string]any{"name": "duration_nano", "signal": "traces", "fieldContext": "span", "fieldDataType": "float64"},
},
},
},
},
{
Name: "logs_list_generated_name",
Summary: "Logs list view with a generated name",
Description: "A list panel over error logs of one service. generateName lets the server derive the machine name from displayName; the display preferences set the row format and font size.",
Value: map[string]any{
"generateName": true,
"source": "logs",
"schemaVersion": "v2",
"spec": map[string]any{
"displayName": "Payment errors",
"panelType": "list",
"requestType": "raw",
"queries": []any{
map[string]any{
"type": "builder_query",
"spec": map[string]any{
"name": "A",
"signal": "logs",
"filter": map[string]any{"expression": "service.name = 'paymentservice' AND severity_text = 'ERROR'"},
"order": []any{
map[string]any{"key": map[string]any{"name": "timestamp"}, "direction": "desc"},
map[string]any{"key": map[string]any{"name": "id"}, "direction": "desc"},
},
"limit": 100,
},
},
},
"selectedFields": []any{
map[string]any{"name": "service.name", "signal": "logs", "fieldContext": "resource", "fieldDataType": "string"},
map[string]any{"name": "severity_text", "signal": "logs", "fieldContext": "log", "fieldDataType": "string"},
},
"display": map[string]any{
"format": "list",
"fontSize": "small",
"maxLines": 2,
},
},
},
},
{
Name: "metrics_time_series",
Summary: "Metrics graph view",
Description: "A graph panel plotting the per-second request rate of a counter, summed across series at one minute resolution.",
Value: map[string]any{
"name": "http-request-rate",
"source": "metrics",
"schemaVersion": "v2",
"spec": map[string]any{
"displayName": "HTTP request rate",
"panelType": "graph",
"requestType": "time_series",
"queries": []any{
map[string]any{
"type": "builder_query",
"spec": map[string]any{
"name": "A",
"signal": "metrics",
"stepInterval": 60,
"aggregations": []any{map[string]any{"metricName": "http_requests_total", "timeAggregation": "rate", "spaceAggregation": "sum"}},
"groupBy": []any{map[string]any{"name": "service.name", "fieldContext": "resource", "fieldDataType": "string"}},
},
},
},
"selectedFields": []any{},
},
},
},
{
Name: "meter_time_series",
Summary: "Cost meter graph view",
Description: "A graph panel on the meter explorer. Meter views are queried as metrics with the builder spec source set to meter; the meter rolls up hourly, so the step is 3600 seconds.",
Value: map[string]any{
"name": "log-ingestion-volume",
"source": "meter",
"schemaVersion": "v2",
"spec": map[string]any{
"displayName": "Log ingestion volume",
"panelType": "graph",
"requestType": "time_series",
"queries": []any{
map[string]any{
"type": "builder_query",
"spec": map[string]any{
"name": "A",
"signal": "metrics",
"source": "meter",
"stepInterval": 3600,
"aggregations": []any{map[string]any{"metricName": "signoz.meter.log.size", "timeAggregation": "increase", "spaceAggregation": "sum"}},
},
},
},
"selectedFields": []any{},
},
},
},
}
}
// updatableSavedViewExamples returns example payloads attached to the saved view
// update endpoint. The update replaces the whole spec; the machine name is immutable.
func updatableSavedViewExamples() []handler.OpenAPIExample {
return []handler.OpenAPIExample{
{
Name: "traces_list_widen_filter",
Summary: "Replace a traces list view",
Description: "Replaces the spec of an existing traces list view with a wider latency filter and one more column. Every field of spec must be sent again; the machine name stays as created.",
Value: map[string]any{
"source": "traces",
"schemaVersion": "v2",
"spec": map[string]any{
"displayName": "Slow checkout traces (over 250ms)",
"panelType": "list",
"requestType": "raw",
"queries": []any{
map[string]any{
"type": "builder_query",
"spec": map[string]any{
"name": "A",
"signal": "traces",
"filter": map[string]any{"expression": "service.name = 'checkoutservice' AND duration_nano > 250000000"},
"order": []any{map[string]any{"key": map[string]any{"name": "timestamp"}, "direction": "desc"}},
"limit": 100,
},
},
},
"selectedFields": []any{
map[string]any{"name": "service.name", "signal": "traces", "fieldContext": "resource", "fieldDataType": "string"},
map[string]any{"name": "name", "signal": "traces", "fieldContext": "span", "fieldDataType": "string"},
map[string]any{"name": "duration_nano", "signal": "traces", "fieldContext": "span", "fieldDataType": "float64"},
map[string]any{"name": "http.status_code", "signal": "traces", "fieldContext": "attribute", "fieldDataType": "string"},
},
},
},
},
}
}

View File

@@ -0,0 +1,32 @@
package signozapiserver
import (
"encoding/json"
"testing"
"github.com/SigNoz/signoz/pkg/types/savedviewtypes"
"github.com/stretchr/testify/require"
)
// The examples are advertised in the OpenAPI spec as payloads the API accepts,
// so each must decode through the same validation the handlers run.
func TestSavedViewExamplesAreValidPayloads(t *testing.T) {
for _, ex := range postableSavedViewExamples() {
t.Run("create/"+ex.Name, func(t *testing.T) {
raw, err := json.Marshal(ex.Value)
require.NoError(t, err)
var postable savedviewtypes.PostableSavedView
require.NoError(t, json.Unmarshal(raw, &postable))
require.NoError(t, postable.Validate())
})
}
for _, ex := range updatableSavedViewExamples() {
t.Run("update/"+ex.Name, func(t *testing.T) {
raw, err := json.Marshal(ex.Value)
require.NoError(t, err)
var updatable savedviewtypes.UpdatableSavedView
require.NoError(t, json.Unmarshal(raw, &updatable))
require.NoError(t, updatable.Validate())
})
}
}

View File

@@ -47,9 +47,9 @@ const (
type DeprecatedGettableAlert struct {
*model.Alert
Status types.AlertStatus `json:"status"`
Receivers []string `json:"receivers"`
Fingerprint string `json:"fingerprint"`
Status types.AlertStatus `json:"status" description:"The alert state and the silences and inhibitions suppressing it."`
Receivers []string `json:"receivers" description:"The display names of the channels the alert is routed to."`
Fingerprint string `json:"fingerprint" description:"The alert fingerprint, a hash of its label set."`
}
type DeprecatedGettableAlerts = []*DeprecatedGettableAlert

View File

@@ -36,11 +36,11 @@ type Channel struct {
// DisplayName is the receiver name inside Data and what policies and rules
// reference, so it keeps the v1 wire tag and Name stays off the v1 contract.
Name string `json:"-" bun:"name"`
DisplayName string `json:"name" required:"true" bun:"display_name"`
DisplayName string `json:"name" required:"true" bun:"display_name" description:"The channel name. Alert rules and routing policies reference it."`
// TODO: type this as ChannelKind once v1 is gone.
Type string `json:"type" required:"true" bun:"type"`
Data string `json:"data" required:"true" bun:"data"`
OrgID string `json:"orgId" required:"true" bun:"org_id"`
Type string `json:"type" required:"true" bun:"type" description:"The channel kind, such as slack."`
Data string `json:"data" required:"true" bun:"data" description:"The channel configuration as a JSON document."`
OrgID string `json:"orgId" required:"true" bun:"org_id" description:"The organization the channel belongs to."`
// Spec is the v2 spec a read returns, of the kind Type names. A v2 write
// stores it as the caller wrote it and a v1 write derives it from the

View File

@@ -14,10 +14,10 @@ import (
// credentials and TLS settings come from the deployment's global config, so a
// channel can only choose recipients and body.
type ChannelEmailConfig struct {
SendResolved *bool `json:"sendResolved,omitempty"`
To string `json:"to" required:"true"`
HTML valuer.UnsetOrNonEmptyString `json:"html,omitzero"`
Headers map[string]string `json:"headers,omitzero"`
SendResolved *bool `json:"sendResolved,omitempty" description:"Also notify when the alert resolves. Defaults to false."`
To string `json:"to" required:"true" description:"The recipient addresses, comma separated."`
HTML valuer.UnsetOrNonEmptyString `json:"html,omitzero" description:"Alertmanager template for the HTML body; defaults to the Alertmanager email default."`
Headers map[string]string `json:"headers,omitzero" description:"Extra SMTP headers keyed by canonical MIME header name, such as Subject."`
}
func (c *ChannelEmailConfig) UnmarshalJSON(data []byte) error {

View File

@@ -8,10 +8,10 @@ import (
)
type ChannelGoogleChatConfig struct {
SendResolved *bool `json:"sendResolved,omitempty"`
WebhookURL string `json:"webhookUrl" required:"true" format:"password"`
Title valuer.UnsetOrNonEmptyString `json:"title,omitzero"`
Text valuer.UnsetOrNonEmptyString `json:"text,omitzero"`
SendResolved *bool `json:"sendResolved,omitempty" description:"Also notify when the alert resolves. Defaults to false."`
WebhookURL string `json:"webhookUrl" required:"true" format:"password" description:"The Google Chat space webhook URL. A secret; returned by create, update and fetch by ID, never by list."`
Title valuer.UnsetOrNonEmptyString `json:"title,omitzero" description:"Alertmanager template for the message title; defaults to the SigNoz Google Chat default."`
Text valuer.UnsetOrNonEmptyString `json:"text,omitzero" description:"Alertmanager template for the message body; defaults to the SigNoz Google Chat default."`
}
func (c *ChannelGoogleChatConfig) UnmarshalJSON(data []byte) error {
@@ -71,11 +71,11 @@ func newChannelGoogleChatConfigFromReceiver(name string, receiver *Receiver) (Ch
type GoogleChatReceiverConfig struct {
config.NotifierConfig `yaml:",inline" json:",inline"`
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty" description:"HTTP client settings for the webhook call."`
WebhookURL *config.SecretURL `yaml:"webhook_url,omitempty" json:"webhook_url,omitempty"`
Title string `yaml:"title,omitempty" json:"title,omitempty"`
Text string `yaml:"text,omitempty" json:"text,omitempty"`
WebhookURL *config.SecretURL `yaml:"webhook_url,omitempty" json:"webhook_url,omitempty" description:"The Google Chat space webhook URL."`
Title string `yaml:"title,omitempty" json:"title,omitempty" description:"Alertmanager template for the message title."`
Text string `yaml:"text,omitempty" json:"text,omitempty" description:"Alertmanager template for the message body."`
}
var DefaultGoogleChatReceiverConfig = GoogleChatReceiverConfig{

View File

@@ -10,12 +10,12 @@ import (
)
type ChannelIncidentIOConfig struct {
SendResolved *bool `json:"sendResolved,omitempty"`
URL string `json:"url" required:"true"`
Token string `json:"token" required:"true" format:"password"`
Title valuer.UnsetOrNonEmptyString `json:"title,omitzero"`
Description valuer.UnsetOrNonEmptyString `json:"description,omitzero"`
Metadata map[string]string `json:"metadata,omitzero"`
SendResolved *bool `json:"sendResolved,omitempty" description:"Also notify when the alert resolves. Defaults to false."`
URL string `json:"url" required:"true" description:"The alert source HTTP alert events endpoint copied from its setup page."`
Token string `json:"token" required:"true" format:"password" description:"The alert source secret token, without the Bearer prefix. A secret; returned by create, update and fetch by ID, never by list."`
Title valuer.UnsetOrNonEmptyString `json:"title,omitzero" description:"Alertmanager template for the alert title; defaults to the SigNoz incident.io default."`
Description valuer.UnsetOrNonEmptyString `json:"description,omitzero" description:"Alertmanager template for the alert description, rendered as markdown; defaults to the SigNoz incident.io default."`
Metadata map[string]string `json:"metadata,omitzero" description:"Extra event metadata keyed by name, merged over the group's common labels; values are Alertmanager templates."`
}
func (c *ChannelIncidentIOConfig) UnmarshalJSON(data []byte) error {
@@ -102,15 +102,15 @@ const (
type IncidentIOReceiverConfig struct {
config.NotifierConfig `yaml:",inline" json:",inline"`
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty" description:"HTTP client settings for the API call."`
URL string `yaml:"url,omitempty" json:"url,omitempty"`
Token config.Secret `yaml:"token,omitempty" json:"token,omitempty"`
Title string `yaml:"title,omitempty" json:"title,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
URL string `yaml:"url,omitempty" json:"url,omitempty" description:"The alert source HTTP alert events endpoint."`
Token config.Secret `yaml:"token,omitempty" json:"token,omitempty" description:"The alert source secret token, without the Bearer prefix."`
Title string `yaml:"title,omitempty" json:"title,omitempty" description:"Alertmanager template for the alert title."`
Description string `yaml:"description,omitempty" json:"description,omitempty" description:"Alertmanager template for the alert description."`
// Metadata is merged into the event's metadata on top of the group's common
// labels (channel wins on key clash). Values are template-expanded.
Metadata map[string]string `yaml:"metadata,omitempty" json:"metadata,omitempty"`
Metadata map[string]string `yaml:"metadata,omitempty" json:"metadata,omitempty" description:"Extra event metadata keyed by name, merged over the group's common labels; values are Alertmanager templates."`
}
// send_resolved has no omitempty upstream, so a var default here is overwritten

View File

@@ -13,24 +13,24 @@ import (
)
type ChannelJiraConfig struct {
SendResolved *bool `json:"sendResolved,omitempty"`
SendResolved *bool `json:"sendResolved,omitempty" description:"Also notify when the alert resolves. Defaults to false."`
// Site is the Jira Cloud base URL, https://<site>.atlassian.net. Only Jira
// Cloud is supported; the REST base is derived from it.
Site string `json:"site" required:"true"`
Project string `json:"project" required:"true"`
IssueType string `json:"issueType" required:"true"`
Summary valuer.UnsetOrNonEmptyString `json:"summary,omitzero"`
Description valuer.UnsetOrNonEmptyString `json:"description,omitzero"`
Priority string `json:"priority"`
Labels []string `json:"labels,omitzero"`
ResolveTransition string `json:"resolveTransition"`
ReopenTransition string `json:"reopenTransition"`
ReopenDuration valuer.UnsetOrNonEmptyString `json:"reopenDuration,omitzero"`
WontFixResolution string `json:"wontFixResolution"`
CustomFields map[string]any `json:"customFields,omitzero"`
Site string `json:"site" required:"true" description:"The Jira Cloud base URL, https://<site>.atlassian.net; only Jira Cloud is supported."`
Project string `json:"project" required:"true" description:"The key of the project issues are created in."`
IssueType string `json:"issueType" required:"true" description:"The issue type name, such as Bug."`
Summary valuer.UnsetOrNonEmptyString `json:"summary,omitzero" description:"Alertmanager template for the issue summary; defaults to the SigNoz Jira default."`
Description valuer.UnsetOrNonEmptyString `json:"description,omitzero" description:"Alertmanager template for the issue description, rendered as markdown; defaults to the SigNoz Jira default."`
Priority string `json:"priority" description:"The priority name assigned to new issues."`
Labels []string `json:"labels,omitzero" description:"Labels added to every issue."`
ResolveTransition string `json:"resolveTransition" description:"The transition applied to the issue when the alert resolves."`
ReopenTransition string `json:"reopenTransition" description:"The transition applied when an alert fires again on a resolved issue."`
ReopenDuration valuer.UnsetOrNonEmptyString `json:"reopenDuration,omitzero" description:"How long after resolution a firing alert reopens the issue instead of creating a new one, as a Prometheus duration in canonical form such as 3d; defaults to 3d."`
WontFixResolution string `json:"wontFixResolution" description:"The resolution name that stops an issue from being reopened."`
CustomFields map[string]any `json:"customFields,omitzero" description:"Extra issue fields keyed by Jira field ID. Stored, but not currently included in the issue Jira receives."`
Email string `json:"email" required:"true"`
APIToken string `json:"apiToken" required:"true" format:"password"`
Email string `json:"email" required:"true" description:"The Atlassian account email used for basic auth."`
APIToken string `json:"apiToken" required:"true" format:"password" description:"The Atlassian API token paired with email. A secret; returned by create, update and fetch by ID, never by list."`
}
// UnmarshalJSON seeds send_resolved off, as JiraReceiverConfig does.
@@ -193,19 +193,19 @@ const (
type JiraReceiverConfig struct {
config.NotifierConfig `yaml:",inline"`
Site string `json:"site,omitempty" yaml:"site,omitempty"`
Project string `json:"project,omitempty" yaml:"project,omitempty"`
IssueType string `json:"issue_type,omitempty" yaml:"issue_type,omitempty"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Priority string `json:"priority,omitempty" yaml:"priority,omitempty"`
Labels []string `json:"labels,omitempty" yaml:"labels,omitempty"`
ResolveTransition string `json:"resolve_transition,omitempty" yaml:"resolve_transition,omitempty"`
ReopenTransition string `json:"reopen_transition,omitempty" yaml:"reopen_transition,omitempty"`
ReopenDuration model.Duration `json:"reopen_duration" yaml:"reopen_duration"`
WontFixResolution string `json:"wont_fix_resolution,omitempty" yaml:"wont_fix_resolution,omitempty"`
CustomFields map[string]any `json:"custom_fields,omitempty" yaml:"custom_fields,omitempty"`
HTTPConfig *commoncfg.HTTPClientConfig `json:"http_config,omitempty" yaml:"http_config,omitempty"`
Site string `json:"site,omitempty" yaml:"site,omitempty" description:"The Jira Cloud base URL."`
Project string `json:"project,omitempty" yaml:"project,omitempty" description:"The key of the project issues are created in."`
IssueType string `json:"issue_type,omitempty" yaml:"issue_type,omitempty" description:"The issue type name."`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty" description:"Alertmanager template for the issue summary."`
Description string `json:"description,omitempty" yaml:"description,omitempty" description:"Alertmanager template for the issue description."`
Priority string `json:"priority,omitempty" yaml:"priority,omitempty" description:"The priority name assigned to new issues."`
Labels []string `json:"labels,omitempty" yaml:"labels,omitempty" description:"Labels added to every issue."`
ResolveTransition string `json:"resolve_transition,omitempty" yaml:"resolve_transition,omitempty" description:"The transition applied when the alert resolves."`
ReopenTransition string `json:"reopen_transition,omitempty" yaml:"reopen_transition,omitempty" description:"The transition applied when an alert fires again on a resolved issue."`
ReopenDuration model.Duration `json:"reopen_duration" yaml:"reopen_duration" description:"How long after resolution a firing alert reopens the issue; a Prometheus duration, at least 1m."`
WontFixResolution string `json:"wont_fix_resolution,omitempty" yaml:"wont_fix_resolution,omitempty" description:"The resolution name that stops an issue from being reopened."`
CustomFields map[string]any `json:"custom_fields,omitempty" yaml:"custom_fields,omitempty" description:"Extra issue fields keyed by Jira field ID. Stored, but not currently included in the issue Jira receives."`
HTTPConfig *commoncfg.HTTPClientConfig `json:"http_config,omitempty" yaml:"http_config,omitempty" description:"HTTP client settings; basic auth carries the account email and API token."`
}
func (c *JiraReceiverConfig) UnmarshalYAML(unmarshal func(any) error) error {

View File

@@ -10,13 +10,12 @@ import (
// ChannelJSMOpsConfig carries no API URL: JSM Ops is a single global gateway
// keyed by the integration API key, which the notifier pins itself.
type ChannelJSMOpsConfig struct {
SendResolved *bool `json:"sendResolved,omitempty"`
APIKey string `json:"apiKey" required:"true" format:"password"`
Message valuer.UnsetOrNonEmptyString `json:"message,omitzero"`
Description valuer.UnsetOrNonEmptyString `json:"description,omitzero"`
Priority string `json:"priority"`
// Tags is the comma-separated list JSM Ops attaches to the alert.
Tags valuer.UnsetOrNonEmptyString `json:"tags,omitzero"`
SendResolved *bool `json:"sendResolved,omitempty" description:"Also notify when the alert resolves. Defaults to false."`
APIKey string `json:"apiKey" required:"true" format:"password" description:"The JSM Ops integration API key. A secret; returned by create, update and fetch by ID, never by list."`
Message valuer.UnsetOrNonEmptyString `json:"message,omitzero" description:"Alertmanager template for the alert message; defaults to the SigNoz JSM Ops default."`
Description valuer.UnsetOrNonEmptyString `json:"description,omitzero" description:"Alertmanager template for the alert description; defaults to the SigNoz JSM Ops default."`
Priority string `json:"priority" description:"The alert priority, P1 to P5."`
Tags valuer.UnsetOrNonEmptyString `json:"tags,omitzero" description:"Comma-separated tags attached to the alert; defaults to signoz."`
}
func (c *ChannelJSMOpsConfig) UnmarshalJSON(data []byte) error {
@@ -108,13 +107,13 @@ const (
type JSMOpsReceiverConfig struct {
config.NotifierConfig `yaml:",inline" json:",inline"`
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty" description:"HTTP client settings for the API call."`
APIKey config.Secret `yaml:"api_key,omitempty" json:"api_key,omitempty"`
Message string `yaml:"message,omitempty" json:"message,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
Tags string `yaml:"tags,omitempty" json:"tags,omitempty"`
APIKey config.Secret `yaml:"api_key,omitempty" json:"api_key,omitempty" description:"The JSM Ops integration API key."`
Message string `yaml:"message,omitempty" json:"message,omitempty" description:"Alertmanager template for the alert message."`
Description string `yaml:"description,omitempty" json:"description,omitempty" description:"Alertmanager template for the alert description."`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty" description:"The alert priority, P1 to P5."`
Tags string `yaml:"tags,omitempty" json:"tags,omitempty" description:"Comma-separated tags attached to the alert."`
}
// send_resolved has no omitempty upstream, so a var default here is overwritten

View File

@@ -7,10 +7,10 @@ import (
)
type ChannelMSTeamsConfig struct {
SendResolved *bool `json:"sendResolved,omitempty"`
WebhookURL string `json:"webhookUrl" required:"true" format:"password"`
Title valuer.UnsetOrNonEmptyString `json:"title,omitzero"`
Text valuer.UnsetOrNonEmptyString `json:"text,omitzero"`
SendResolved *bool `json:"sendResolved,omitempty" description:"Also notify when the alert resolves. Defaults to true."`
WebhookURL string `json:"webhookUrl" required:"true" format:"password" description:"The Teams incoming webhook URL. A secret; returned by create, update and fetch by ID, never by list."`
Title valuer.UnsetOrNonEmptyString `json:"title,omitzero" description:"Alertmanager template for the card title; defaults to the Alertmanager Teams default."`
Text valuer.UnsetOrNonEmptyString `json:"text,omitzero" description:"Alertmanager template for the card body; defaults to the Alertmanager Teams default."`
}
func (c *ChannelMSTeamsConfig) UnmarshalJSON(data []byte) error {

View File

@@ -7,14 +7,14 @@ import (
)
type ChannelOpsgenieConfig struct {
SendResolved *bool `json:"sendResolved,omitempty"`
APIKey string `json:"apiKey" required:"true" format:"password"`
APIURL string `json:"apiUrl"`
Message valuer.UnsetOrNonEmptyString `json:"message,omitzero"`
Description valuer.UnsetOrNonEmptyString `json:"description,omitzero"`
Source valuer.UnsetOrNonEmptyString `json:"source,omitzero"`
Details map[string]string `json:"details,omitzero"`
Priority string `json:"priority"`
SendResolved *bool `json:"sendResolved,omitempty" description:"Also notify when the alert resolves. Defaults to true."`
APIKey string `json:"apiKey" required:"true" format:"password" description:"The Opsgenie API integration key. A secret; returned by create, update and fetch by ID, never by list."`
APIURL string `json:"apiUrl" description:"The Opsgenie API host; empty uses the global Alertmanager default."`
Message valuer.UnsetOrNonEmptyString `json:"message,omitzero" description:"Alertmanager template for the alert message; defaults to the Alertmanager Opsgenie default."`
Description valuer.UnsetOrNonEmptyString `json:"description,omitzero" description:"Alertmanager template for the alert description; defaults to the Alertmanager Opsgenie default."`
Source valuer.UnsetOrNonEmptyString `json:"source,omitzero" description:"Alertmanager template for the alert source; defaults to the Alertmanager Opsgenie default."`
Details map[string]string `json:"details,omitzero" description:"Custom alert details keyed by name, each an Alertmanager template."`
Priority string `json:"priority" description:"The alert priority, P1 to P5."`
}
func (c *ChannelOpsgenieConfig) UnmarshalJSON(data []byte) error {

View File

@@ -7,18 +7,18 @@ import (
)
type ChannelPagerdutyConfig struct {
SendResolved *bool `json:"sendResolved,omitempty"`
RoutingKey string `json:"routingKey" required:"true" format:"password"`
URL string `json:"url"`
Source valuer.UnsetOrNonEmptyString `json:"source,omitzero"`
Client valuer.UnsetOrNonEmptyString `json:"client,omitzero"`
ClientURL valuer.UnsetOrNonEmptyString `json:"clientUrl,omitzero"`
Description valuer.UnsetOrNonEmptyString `json:"description,omitzero"`
Severity string `json:"severity"`
Component string `json:"component"`
Group string `json:"group"`
Class string `json:"class"`
Details map[string]string `json:"details,omitzero"`
SendResolved *bool `json:"sendResolved,omitempty" description:"Also notify when the alert resolves. Defaults to true."`
RoutingKey string `json:"routingKey" required:"true" format:"password" description:"The Events API v2 integration key. A secret; returned by create, update and fetch by ID, never by list."`
URL string `json:"url" description:"The Events API endpoint; empty uses the PagerDuty default."`
Source valuer.UnsetOrNonEmptyString `json:"source,omitzero" description:"Alertmanager template for the unique location of the affected system; defaults to client."`
Client valuer.UnsetOrNonEmptyString `json:"client,omitzero" description:"Alertmanager template for the client identifier; defaults to the Alertmanager PagerDuty default."`
ClientURL valuer.UnsetOrNonEmptyString `json:"clientUrl,omitzero" description:"Alertmanager template for the client URL; defaults to the Alertmanager PagerDuty default."`
Description valuer.UnsetOrNonEmptyString `json:"description,omitzero" description:"Alertmanager template for the incident summary; defaults to the Alertmanager PagerDuty default."`
Severity string `json:"severity" description:"The incident severity: critical, error, warning or info."`
Component string `json:"component" description:"The component of the source machine that is responsible for the event."`
Group string `json:"group" description:"The logical grouping of components the event belongs to."`
Class string `json:"class" description:"The class or type of the event."`
Details map[string]string `json:"details,omitzero" description:"Custom details keyed by name, each an Alertmanager template; defaults to the Alertmanager firing and resolved summary set."`
}
func (c *ChannelPagerdutyConfig) UnmarshalJSON(data []byte) error {

View File

@@ -7,43 +7,43 @@ import (
)
type ChannelSlackConfig struct {
SendResolved *bool `json:"sendResolved,omitempty"`
APIURL string `json:"apiUrl" required:"true" format:"password"`
Channel string `json:"channel"`
Title valuer.UnsetOrNonEmptyString `json:"title,omitzero"`
Text valuer.UnsetOrNonEmptyString `json:"text,omitzero"`
Color valuer.UnsetOrNonEmptyString `json:"color,omitzero"`
TitleLink valuer.UnsetOrNonEmptyString `json:"titleLink,omitzero"`
Pretext valuer.UnsetOrNonEmptyString `json:"pretext,omitzero"`
Fallback valuer.UnsetOrNonEmptyString `json:"fallback,omitzero"`
Footer valuer.UnsetOrNonEmptyString `json:"footer,omitzero"`
Fields []ChannelSlackField `json:"fields,omitzero"`
Actions []ChannelSlackAction `json:"actions,omitzero"`
SendResolved *bool `json:"sendResolved,omitempty" description:"Also notify when the alert resolves. Defaults to false."`
APIURL string `json:"apiUrl" required:"true" format:"password" description:"The Slack incoming webhook URL. A secret; returned by create, update and fetch by ID, never by list."`
Channel string `json:"channel" description:"The channel or user to post to, such as #alerts; empty uses the webhook default."`
Title valuer.UnsetOrNonEmptyString `json:"title,omitzero" description:"Alertmanager template for the message title; defaults to the Alertmanager Slack default."`
Text valuer.UnsetOrNonEmptyString `json:"text,omitzero" description:"Alertmanager template for the message body; defaults to the Alertmanager Slack default."`
Color valuer.UnsetOrNonEmptyString `json:"color,omitzero" description:"Alertmanager template for the attachment color; defaults to the Alertmanager Slack default."`
TitleLink valuer.UnsetOrNonEmptyString `json:"titleLink,omitzero" description:"Alertmanager template for the link the title opens; defaults to the Alertmanager Slack default."`
Pretext valuer.UnsetOrNonEmptyString `json:"pretext,omitzero" description:"Alertmanager template for the text shown above the attachment; defaults to the Alertmanager Slack default."`
Fallback valuer.UnsetOrNonEmptyString `json:"fallback,omitzero" description:"Alertmanager template for the plain-text summary shown where attachments cannot render; defaults to the Alertmanager Slack default."`
Footer valuer.UnsetOrNonEmptyString `json:"footer,omitzero" description:"Alertmanager template for the attachment footer; defaults to the Alertmanager Slack default."`
Fields []ChannelSlackField `json:"fields,omitzero" description:"Attachment fields appended to the message; each requires title and value."`
Actions []ChannelSlackAction `json:"actions,omitzero" description:"Attachment buttons; each requires type and text plus url or name."`
}
type ChannelSlackField struct {
Title string `json:"title" required:"true"`
Value string `json:"value" required:"true"`
Short *bool `json:"short,omitempty"`
Title string `json:"title" required:"true" description:"The field title."`
Value string `json:"value" required:"true" description:"The field value; an Alertmanager template."`
Short *bool `json:"short,omitempty" description:"Render the field side by side with other short fields."`
}
// ChannelSlackAction is a link button when URL is set, otherwise a message
// button that needs Name. Upstream clears whichever side is not in use.
type ChannelSlackAction struct {
Type string `json:"type" required:"true"`
Text string `json:"text" required:"true"`
URL string `json:"url"`
Style string `json:"style"`
Name string `json:"name"`
Value string `json:"value"`
Confirm *ChannelSlackConfirmation `json:"confirm,omitempty"`
Type string `json:"type" required:"true" description:"The Slack action type, normally button."`
Text string `json:"text" required:"true" description:"The button label."`
URL string `json:"url" description:"The link the button opens; required unless name is set."`
Style string `json:"style" description:"The button style: default, primary or danger."`
Name string `json:"name" description:"The action name Slack posts back for a message button; required unless url is set."`
Value string `json:"value" description:"The value Slack posts back with a message button."`
Confirm *ChannelSlackConfirmation `json:"confirm,omitempty" description:"A confirmation dialog shown before the action runs."`
}
type ChannelSlackConfirmation struct {
Text string `json:"text" required:"true"`
Title string `json:"title"`
OkText string `json:"okText"`
DismissText string `json:"dismissText"`
Text string `json:"text" required:"true" description:"The dialog body."`
Title string `json:"title" description:"The dialog title."`
OkText string `json:"okText" description:"The label of the confirm button."`
DismissText string `json:"dismissText" description:"The label of the cancel button."`
}
func (c *ChannelSlackConfig) UnmarshalJSON(data []byte) error {

View File

@@ -16,11 +16,11 @@ const bearerAuthorizationType = "Bearer"
// was really a bearer token. Username or Password may be set without the other,
// as upstream allows, but not together with BearerToken.
type ChannelWebhookConfig struct {
SendResolved *bool `json:"sendResolved,omitempty"`
URL string `json:"url" required:"true" format:"password"`
Username string `json:"username"`
Password string `json:"password" format:"password"`
BearerToken string `json:"bearerToken" format:"password"`
SendResolved *bool `json:"sendResolved,omitempty" description:"Also notify when the alert resolves. Defaults to true."`
URL string `json:"url" required:"true" format:"password" description:"The endpoint that receives the Alertmanager webhook payload. A secret; returned by create, update and fetch by ID, never by list."`
Username string `json:"username" description:"The basic auth user; cannot be combined with bearerToken."`
Password string `json:"password" format:"password" description:"The basic auth password; cannot be combined with bearerToken."`
BearerToken string `json:"bearerToken" format:"password" description:"The bearer token sent in the Authorization header; cannot be combined with username or password."`
}
func (c *ChannelWebhookConfig) UnmarshalJSON(data []byte) error {

View File

@@ -18,10 +18,10 @@ import (
// Name is the immutable DNS1123 identity references will point at; DisplayName is the
// free-text label.
type PostableNotificationChannel struct {
Name string `json:"name"`
GenerateName bool `json:"generateName"`
DisplayName string `json:"displayName"`
Config ChannelConfig `json:"config" required:"true"`
Name string `json:"name" description:"The immutable DNS-1123 identifier of the channel (lowercase letters, digits and hyphens, at most 63 characters). Required unless generateName is true, in which case it must be empty. Alert rules and routing policies reference displayName, not name."`
GenerateName bool `json:"generateName" description:"Derive name from displayName plus a random suffix instead of supplying it. Defaults to false."`
DisplayName string `json:"displayName" description:"The label shown in the UI. Alert rules and routing policies reference it, not name. Defaults to name; required when generateName is true."`
Config ChannelConfig `json:"config" required:"true" description:"The provider kind and its spec."`
}
func (p *PostableNotificationChannel) UnmarshalJSON(data []byte) error {
@@ -93,7 +93,7 @@ func (p *PostableNotificationChannel) validateName() error {
// Name is immutable, and DisplayName cannot be edited until the routing
// policies and rules referencing it migrate onto Name.
type UpdatableNotificationChannel struct {
Config ChannelConfig `json:"config" required:"true"`
Config ChannelConfig `json:"config" required:"true" description:"The replacement provider kind and spec. Name and displayName are immutable and not part of the body."`
}
func (u *UpdatableNotificationChannel) UnmarshalJSON(data []byte) error {
@@ -115,7 +115,7 @@ func (u *UpdatableNotificationChannel) Validate() error {
// It carries no name because nothing is persisted: the receiver a test builds
// is thrown away once the notification is delivered.
type TestableNotificationChannel struct {
Config ChannelConfig `json:"config" required:"true"`
Config ChannelConfig `json:"config" required:"true" description:"The replacement provider kind and spec. Name and displayName are immutable and not part of the body."`
}
func (t *TestableNotificationChannel) UnmarshalJSON(data []byte) error {
@@ -166,12 +166,12 @@ func decodeStrict(data []byte, target any) error {
// ════════════════════════════════════════════════════════════════════════
type GettableNotificationChannel struct {
Name string `json:"name" required:"true"`
DisplayName string `json:"displayName" required:"true"`
Config ChannelConfig `json:"config" required:"true"`
ID valuer.UUID `json:"id" required:"true"`
CreatedAt time.Time `json:"createdAt" required:"true"`
UpdatedAt time.Time `json:"updatedAt" required:"true"`
Name string `json:"name" required:"true" description:"The immutable DNS-1123 identifier of the channel. Alert rules and routing policies reference displayName, not name."`
DisplayName string `json:"displayName" required:"true" description:"The label shown in the UI. Alert rules and routing policies reference it, not name."`
Config ChannelConfig `json:"config" required:"true" description:"The provider kind and spec, credentials included."`
ID valuer.UUID `json:"id" required:"true" description:"The channel UUID."`
CreatedAt time.Time `json:"createdAt" required:"true" description:"When the channel was created, as an RFC 3339 timestamp."`
UpdatedAt time.Time `json:"updatedAt" required:"true" description:"When the channel was last updated, as an RFC 3339 timestamp."`
}
// ════════════════════════════════════════════════════════════════════════
@@ -179,19 +179,19 @@ type GettableNotificationChannel struct {
// ════════════════════════════════════════════════════════════════════════
// ListedNotificationChannel carries no configuration. The spec, credentials
// included, is only returned by a fetch by ID.
// included, is returned by create, update and fetch by ID, never by list.
type ListedNotificationChannel struct {
ID valuer.UUID `json:"id" required:"true"`
Name string `json:"name" required:"true"`
DisplayName string `json:"displayName" required:"true"`
Kind ChannelKind `json:"kind" required:"true"`
CreatedAt time.Time `json:"createdAt" required:"true"`
UpdatedAt time.Time `json:"updatedAt" required:"true"`
ID valuer.UUID `json:"id" required:"true" description:"The channel UUID."`
Name string `json:"name" required:"true" description:"The immutable DNS-1123 identifier of the channel. Alert rules and routing policies reference displayName, not name."`
DisplayName string `json:"displayName" required:"true" description:"The label shown in the UI. Alert rules and routing policies reference it, not name."`
Kind ChannelKind `json:"kind" required:"true" description:"The channel provider kind."`
CreatedAt time.Time `json:"createdAt" required:"true" description:"When the channel was created, as an RFC 3339 timestamp."`
UpdatedAt time.Time `json:"updatedAt" required:"true" description:"When the channel was last updated, as an RFC 3339 timestamp."`
}
// ListableNotificationChannel is one page of the channel list. Total counts every
// channel the filter matches, not just the ones on the page.
type ListableNotificationChannel struct {
Channels []*ListedNotificationChannel `json:"channels" required:"true" nullable:"false"`
Total int64 `json:"total" required:"true"`
Channels []*ListedNotificationChannel `json:"channels" required:"true" nullable:"false" description:"The channels on this page, without their configuration; fetch a channel by ID for that."`
Total int64 `json:"total" required:"true" description:"The number of channels the filter matches across all pages."`
}

View File

@@ -9,8 +9,8 @@ import (
)
type ChannelConfig struct {
Kind ChannelKind `json:"kind" required:"true"`
Spec any `json:"spec" required:"true"`
Kind ChannelKind `json:"kind" required:"true" description:"The channel provider kind; one of the supported kinds listed in the enum."`
Spec any `json:"spec" required:"true" description:"The provider-specific configuration; its shape is selected by kind."`
}
func (c *ChannelConfig) UnmarshalJSON(data []byte) error {
@@ -71,8 +71,8 @@ func (c ChannelConfig) Validate() error {
// ChannelConfigVariant names one branch of the union. Each instantiation becomes
// its own OpenAPI component with kind pinned to the one value it accepts.
type ChannelConfigVariant[S any] struct {
Kind string `json:"kind" required:"true"`
Spec S `json:"spec" required:"true"`
Kind string `json:"kind" required:"true" description:"The channel provider kind this variant accepts."`
Spec S `json:"spec" required:"true" description:"The provider-specific configuration for this kind."`
}
func (v ChannelConfigVariant[S]) PrepareJSONSchema(s *jsonschema.Schema) error {

View File

@@ -61,12 +61,12 @@ func (o ChannelListOrder) IsValid() bool {
// ListChannelsParams filters and orders the channel list. Query matches the
// display name case-insensitively.
type ListChannelsParams struct {
Query string `query:"query" json:"query"`
Kind ChannelKind `query:"kind" json:"kind"`
Sort ChannelListSort `query:"sort" json:"sort"`
Order ChannelListOrder `query:"order" json:"order"`
Limit int `query:"limit" json:"limit"`
Offset int `query:"offset" json:"offset"`
Query string `query:"query" json:"query" description:"Case-insensitive match on the display name; at most 1024 characters."`
Kind ChannelKind `query:"kind" json:"kind" description:"Only channels of this provider kind."`
Sort ChannelListSort `query:"sort" json:"sort" description:"The sort column: updated_at (default), created_at or name."`
Order ChannelListOrder `query:"order" json:"order" description:"The sort order: asc or desc (default)."`
Limit int `query:"limit" json:"limit" description:"The page size; defaults to 20 and is capped at 200."`
Offset int `query:"offset" json:"offset" description:"The number of channels to skip; defaults to 0."`
}
func (p *ListChannelsParams) Validate() error {

View File

@@ -45,7 +45,7 @@ func (ChannelRepairAction) Enum() []any {
// RepairChannelParams defaults to a dry run; only apply=true writes anything.
type RepairChannelParams struct {
Apply bool `query:"apply" json:"apply"`
Apply bool `query:"apply" json:"apply" description:"Apply the diagnosed action; defaults to false, a dry run that only reports it."`
}
// ChannelRepair is one channel's diagnosis and the action that makes it
@@ -53,13 +53,13 @@ type RepairChannelParams struct {
// channel itself for none and retype, every part of a split, nothing for a
// delete. Blockers explain why an action was not, or would not be, applied.
type ChannelRepair struct {
ID valuer.UUID `json:"id" required:"true"`
Defect ChannelDefect `json:"defect" required:"true"`
Detail string `json:"detail"`
Action ChannelRepairAction `json:"action" required:"true"`
Blockers []string `json:"blockers,omitempty"`
Channels []*ListedNotificationChannel `json:"channels"`
Applied bool `json:"applied" required:"true"`
ID valuer.UUID `json:"id" required:"true" description:"The UUID of the diagnosed channel."`
Defect ChannelDefect `json:"defect" required:"true" description:"Why v2 cannot read the channel; none when it is readable."`
Detail string `json:"detail" description:"A human-readable explanation of the defect."`
Action ChannelRepairAction `json:"action" required:"true" description:"The repair that makes the channel readable: none, retype, split or delete."`
Blockers []string `json:"blockers,omitempty" description:"Why the action was not, or would not be, applied."`
Channels []*ListedNotificationChannel `json:"channels" description:"The channels that exist once the action is applied."`
Applied bool `json:"applied" required:"true" description:"Whether the action was written; false on a dry run."`
}
// Diagnose reads the channel the way v2 does and reports the first defect in

View File

@@ -29,13 +29,13 @@ type DashboardView struct {
types.Identifiable
types.TimeAuditable
Name string `bun:"name,type:text,notnull" json:"name" required:"true"`
Data DashboardViewData `bun:"data,type:text,notnull" json:"data" required:"true"`
OrgID valuer.UUID `bun:"org_id,type:text,notnull" json:"orgId" required:"true"`
Name string `bun:"name,type:text,notnull" json:"name" required:"true" description:"The view name."`
Data DashboardViewData `bun:"data,type:text,notnull" json:"data" required:"true" description:"The saved list filter and sort."`
OrgID valuer.UUID `bun:"org_id,type:text,notnull" json:"orgId" required:"true" description:"The organization that owns the view."`
}
type DashboardViewData struct {
Version string `json:"version" required:"true"`
Version string `json:"version" required:"true" description:"The view data schema version. Must be v1."`
ListFilter
}
@@ -52,8 +52,8 @@ func (d *DashboardViewData) Validate() error {
// ════════════════════════════════════════════════════════════════════════
type PostableDashboardView struct {
Name string `json:"name" required:"true"`
Data DashboardViewData `json:"data" required:"true"`
Name string `json:"name" required:"true" maxLength:"64" description:"The view name, at most 64 characters."`
Data DashboardViewData `json:"data" required:"true" description:"The list filter and sort to save."`
}
func (p *PostableDashboardView) UnmarshalJSON(data []byte) error {
@@ -103,7 +103,7 @@ func (v *DashboardView) Update(updateable UpdatableDashboardView) {
// ════════════════════════════════════════════════════════════════════════
type ListableDashboardView struct {
Views []*DashboardView `json:"views" required:"true" nullable:"false"`
Views []*DashboardView `json:"views" required:"true" nullable:"false" description:"The saved dashboard list views."`
}
// ════════════════════════════════════════════════════════════════════════

View File

@@ -4,10 +4,10 @@ package dashboardtypes
// references a given metric, along with the group-by and filter labels used for
// that metric so callers can reason about the impact of dropping its labels.
type DashboardPanelRef struct {
DashboardID string `json:"dashboardId" required:"true"`
DashboardName string `json:"dashboardName" required:"true"`
PanelID string `json:"panelId" required:"true"`
PanelName string `json:"panelName" required:"true"`
GroupBy []string `json:"groupBy,omitempty"`
FilterBy []string `json:"filterBy,omitempty"`
DashboardID string `json:"dashboardId" required:"true" description:"The id of the dashboard containing the panel."`
DashboardName string `json:"dashboardName" required:"true" description:"The dashboard name."`
PanelID string `json:"panelId" required:"true" description:"The panel id within the dashboard."`
PanelName string `json:"panelName" required:"true" description:"The panel title."`
GroupBy []string `json:"groupBy,omitempty" description:"The metric labels the panel groups by."`
FilterBy []string `json:"filterBy,omitempty" description:"The metric labels the panel filters on."`
}

View File

@@ -52,9 +52,9 @@ func (o ListOrder) IsValid() bool {
var ErrCodeDashboardListInvalid = errors.MustNewCode("dashboard_list_invalid")
type ListFilter struct {
Query string `query:"query" json:"query"`
Sort ListSort `query:"sort" json:"sort"`
Order ListOrder `query:"order" json:"order"`
Query string `query:"query" json:"query" description:"A filter expression over the reserved keys name, description, created_at, updated_at, created_by and locked, and over tag keys, using the SigNoz filter grammar: comparisons such as =, !=, CONTAINS, LIKE, IN, < and >, single-quoted strings, RFC3339 timestamps in quotes for created_at and updated_at, combined with AND, OR and NOT, for example name CONTAINS 'overview' AND created_at >= '2026-10-21T00:00:00Z'. At most 1024 characters. Empty matches every dashboard."`
Sort ListSort `query:"sort" json:"sort" description:"The sort field: updated_at (default), created_at or name."`
Order ListOrder `query:"order" json:"order" description:"The sort direction: asc or desc (default)."`
}
func (f *ListFilter) Validate() error {
@@ -75,8 +75,8 @@ func (f *ListFilter) Validate() error {
type ListDashboardsV2Params struct {
ListFilter
Limit int `query:"limit"`
Offset int `query:"offset"`
Limit int `query:"limit" description:"The page size. Defaults to 20 and is capped at 200."`
Offset int `query:"offset" description:"The number of dashboards to skip. Defaults to 0."`
}
func (p *ListDashboardsV2Params) Validate() error {
@@ -114,22 +114,22 @@ type listedDashboardV2 struct {
types.TimeAuditable
types.UserAuditable
OrgID valuer.UUID `json:"orgId" required:"true"`
Locked bool `json:"locked" required:"true"`
Source Source `json:"source" required:"true"`
SchemaVersion string `json:"schemaVersion" required:"true"`
Name string `json:"name" required:"true"`
Image string `json:"image,omitempty"`
Tags []*tagtypes.GettableTag `json:"tags" required:"true" nullable:"false"`
Spec listedDashboardV2Spec `json:"spec" required:"true"`
OrgID valuer.UUID `json:"orgId" required:"true" description:"The organization that owns the dashboard."`
Locked bool `json:"locked" required:"true" description:"Whether the dashboard is locked against update and patch."`
Source Source `json:"source" required:"true" description:"Where the dashboard came from: user or integration. System dashboards are not listed."`
SchemaVersion string `json:"schemaVersion" required:"true" description:"The dashboard schema version, v6 for v2 dashboards."`
Name string `json:"name" required:"true" description:"The dashboard's unique, immutable name."`
Image string `json:"image,omitempty" description:"Icon or logo shown in the dashboard list: the path of an icon or logo bundled with the UI under /assets/Icons/ or /assets/Logos/, or a base64 image data URI. Omitted when none is set."`
Tags []*tagtypes.GettableTag `json:"tags" required:"true" nullable:"false" description:"The tags attached to the dashboard."`
Spec listedDashboardV2Spec `json:"spec" required:"true" description:"The display part of the dashboard spec. Panels, variables and layouts are not included in list rows."`
// Legacy marks a dashboard whose stored data is not yet in the v2 (perses)
// schema. Such rows are extracted best-effort from the v1 shape so the list
// still surfaces them; callers should route them to the legacy view.
Legacy bool `json:"legacy" required:"true"`
Legacy bool `json:"legacy" required:"true" description:"Whether the dashboard is still in the v1 schema. It is listed but cannot be fetched or edited through the v2 endpoints."`
}
type listedDashboardV2Spec struct {
Display Display `json:"display,omitempty"`
Display Display `json:"display,omitempty" description:"The dashboard name and description."`
}
func newListedDashboardV2(v2 *DashboardV2) *listedDashboardV2 {
@@ -184,10 +184,10 @@ func newLegacyListedDashboardV2(storable *StorableDashboard, tags []*tagtypes.Ta
}
type ListableDashboardV2 struct {
Dashboards []*listedDashboardV2 `json:"dashboards" required:"true" nullable:"false"`
Total int64 `json:"total" required:"true"`
Tags []*tagtypes.GettableTag `json:"tags" required:"true" nullable:"false"`
ReservedKeywords []DSLKey `json:"reservedKeywords" required:"true" nullable:"false"`
Dashboards []*listedDashboardV2 `json:"dashboards" required:"true" nullable:"false" description:"The dashboards on this page."`
Total int64 `json:"total" required:"true" description:"The number of dashboards matching the filter across every page."`
Tags []*tagtypes.GettableTag `json:"tags" required:"true" nullable:"false" description:"Every dashboard tag in the organization, for building filters."`
ReservedKeywords []DSLKey `json:"reservedKeywords" required:"true" nullable:"false" description:"The filter keys that address dashboard columns rather than tags."`
}
func NewListableDashboardV2(dashboards []*StorableDashboard, total int64, tagsByEntity map[valuer.UUID][]*tagtypes.Tag, allTags []*tagtypes.Tag) *ListableDashboardV2 {
@@ -207,14 +207,14 @@ func NewListableDashboardV2(dashboards []*StorableDashboard, total int64, tagsBy
// state. Only the per-user list endpoint emits this; the pure list omits pins.
type listedDashboardForUserV2 struct {
listedDashboardV2
Pinned bool `json:"pinned" required:"true"`
Pinned bool `json:"pinned" required:"true" description:"Whether the calling user has pinned the dashboard."`
}
type ListableDashboardForUserV2 struct {
Dashboards []*listedDashboardForUserV2 `json:"dashboards" required:"true" nullable:"false"`
Total int64 `json:"total" required:"true"`
Tags []*tagtypes.GettableTag `json:"tags" required:"true" nullable:"false"`
ReservedKeywords []DSLKey `json:"reservedKeywords" required:"true" nullable:"false"`
Dashboards []*listedDashboardForUserV2 `json:"dashboards" required:"true" nullable:"false" description:"The dashboards on this page, each with the calling user's pin state."`
Total int64 `json:"total" required:"true" description:"The number of dashboards matching the filter across every page."`
Tags []*tagtypes.GettableTag `json:"tags" required:"true" nullable:"false" description:"Every dashboard tag in the organization, for building filters."`
ReservedKeywords []DSLKey `json:"reservedKeywords" required:"true" nullable:"false" description:"The filter keys that address dashboard columns rather than tags."`
}
// StorableDashboardWithPinInfo is the per-row shape Store.ListForUser returns: the dashboard

View File

@@ -68,14 +68,14 @@ type DashboardV2 struct {
types.TimeAuditable
types.UserAuditable
OrgID valuer.UUID `json:"orgId" required:"true"`
Locked bool `json:"locked" required:"true"`
Source Source `json:"source" required:"true"`
OrgID valuer.UUID `json:"orgId" required:"true" description:"The organization that owns the dashboard."`
Locked bool `json:"locked" required:"true" description:"Whether the dashboard is locked. A locked dashboard rejects update and patch until it is unlocked."`
Source Source `json:"source" required:"true" description:"Where the dashboard came from: user, integration or system. Only user dashboards can be modified."`
DashboardV2MetadataBase
Name string `json:"name" required:"true"`
Tags []*tagtypes.Tag `json:"tags" required:"true"`
Spec DashboardSpec `json:"spec" required:"true"`
Name string `json:"name" required:"true" description:"The dashboard's unique, immutable name, a DNS-1123 label."`
Tags []*tagtypes.Tag `json:"tags" required:"true" description:"The key/value tags attached to the dashboard, for example key team with value payments."`
Spec DashboardSpec `json:"spec" required:"true" description:"The dashboard content: display, variables, panels, layouts and links."`
}
func (d *DashboardV2) ErrIfNotMutable() error {
@@ -198,8 +198,8 @@ func nextCloneDisplayName(name string) string {
}
type DashboardV2MetadataBase struct {
SchemaVersion string `json:"schemaVersion" required:"true"`
Image string `json:"image"`
SchemaVersion string `json:"schemaVersion" required:"true" pattern:"^v6$" description:"The dashboard schema version. Must be v6."`
Image string `json:"image" description:"Icon or logo shown in the dashboard list: the path of an icon or logo bundled with the UI under /assets/Icons/ or /assets/Logos/ (only the prefix is validated), or a base64 image data URI for png, jpeg, gif, webp, avif or svg. Empty for none."`
}
func (m DashboardV2MetadataBase) validateImage() error {
@@ -222,10 +222,10 @@ func (m DashboardV2MetadataBase) validateImage() error {
type PostableDashboardV2 struct {
DashboardV2MetadataBase
Name string `json:"name"`
GenerateName bool `json:"generateName"`
Tags []tagtypes.PostableTag `json:"tags" required:"true"`
Spec DashboardSpec `json:"spec" required:"true"`
Name string `json:"name" description:"The dashboard's unique name, a DNS-1123 label (lowercase letters, digits and hyphens, at most 63 characters). Immutable after creation. Must be empty when generateName is true."`
GenerateName bool `json:"generateName" description:"When true the server derives name from spec.display.name plus a random suffix. Requires an empty name and a non-empty spec.display.name."`
Tags []tagtypes.PostableTag `json:"tags" required:"true" maxItems:"10" description:"Up to 10 key/value tags, for example key team with value payments. Keys must not be one of the reserved list filter keys: name, description, created_at, updated_at, created_by, locked, source. Use [] for none."`
Spec DashboardSpec `json:"spec" required:"true" description:"The dashboard content: display, variables, panels, layouts and links."`
}
func (postable PostableDashboardV2) NewDashboardV2(orgID valuer.UUID, createdBy string, source Source) (*DashboardV2, error) {
@@ -371,14 +371,14 @@ type GettableDashboardV2 struct {
types.TimeAuditable
types.UserAuditable
OrgID valuer.UUID `json:"orgId" required:"true"`
Locked bool `json:"locked" required:"true"`
Source Source `json:"source" required:"true"`
OrgID valuer.UUID `json:"orgId" required:"true" description:"The organization that owns the dashboard."`
Locked bool `json:"locked" required:"true" description:"Whether the dashboard is locked. A locked dashboard rejects update and patch until it is unlocked."`
Source Source `json:"source" required:"true" description:"Where the dashboard came from: user, integration or system. Only user dashboards can be modified."`
DashboardV2MetadataBase
Name string `json:"name" required:"true"`
Tags []*tagtypes.GettableTag `json:"tags" required:"true"`
Spec DashboardSpec `json:"spec" required:"true"`
Name string `json:"name" required:"true" description:"The dashboard's unique, immutable name, a DNS-1123 label."`
Tags []*tagtypes.GettableTag `json:"tags" required:"true" description:"The key/value tags attached to the dashboard, for example key team with value payments."`
Spec DashboardSpec `json:"spec" required:"true" description:"The dashboard content: display, variables, panels, layouts and links."`
}
func (d DashboardV2) ToGettableDashboardV2() GettableDashboardV2 {
@@ -402,14 +402,14 @@ type GettableSystemDashboard struct {
types.TimeAuditable
types.UserAuditable
OrgID valuer.UUID `json:"orgId" required:"true"`
Locked bool `json:"locked" required:"true"`
Source Source `json:"source" required:"true"`
OrgID valuer.UUID `json:"orgId" required:"true" description:"The organization that owns the dashboard."`
Locked bool `json:"locked" required:"true" description:"Whether the dashboard is locked. A locked dashboard rejects update and patch until it is unlocked."`
Source Source `json:"source" required:"true" description:"Where the dashboard came from: user, integration or system. Only user dashboards can be modified."`
DashboardV2MetadataBase
Name string `json:"name" required:"true"`
Tags []*tagtypes.GettableTag `json:"tags" required:"true"`
Spec DashboardSpec `json:"spec" required:"true"`
Name string `json:"name" required:"true" description:"The dashboard's unique, immutable name, a DNS-1123 label."`
Tags []*tagtypes.GettableTag `json:"tags" required:"true" description:"The key/value tags attached to the dashboard, for example key team with value payments."`
Spec DashboardSpec `json:"spec" required:"true" description:"The dashboard content: display, variables, panels, layouts and links."`
}
func (d DashboardV2) ToGettableSystemDashboard() GettableSystemDashboard {
@@ -456,9 +456,9 @@ type StorableDashboardV2Metadata = DashboardV2MetadataBase
type UpdatableDashboardV2 struct {
DashboardV2MetadataBase
Name string `json:"name" required:"true"`
Tags []tagtypes.PostableTag `json:"tags" required:"true"`
Spec DashboardSpec `json:"spec" required:"true"`
Name string `json:"name" required:"true" description:"The dashboard's current name. The name is immutable, so a different value is rejected."`
Tags []tagtypes.PostableTag `json:"tags" required:"true" maxItems:"10" description:"The full replacement tag set, up to 10 key/value tags, for example key team with value payments. Keys must not be one of the reserved list filter keys: name, description, created_at, updated_at, created_by, locked, source."`
Spec DashboardSpec `json:"spec" required:"true" description:"The full replacement dashboard content: display, variables, panels, layouts and links."`
}
func (u *UpdatableDashboardV2) UnmarshalJSON(data []byte) error {

View File

@@ -21,13 +21,13 @@ import (
// dropped: SigNoz never reads it (queries carry their own signal/source), so
// the drift test allowlists it as an intentional omission.
type DashboardSpec struct {
Display Display `json:"display" required:"true"`
Variables []Variable `json:"variables" required:"true" nullable:"false"`
Panels map[string]*Panel `json:"panels" required:"true" nullable:"false"`
Layouts []Layout `json:"layouts" required:"true" nullable:"false"`
Duration common.DurationString `json:"duration"`
RefreshInterval common.DurationString `json:"refreshInterval"`
Links []Link `json:"links,omitzero"`
Display Display `json:"display" required:"true" description:"The dashboard title and description."`
Variables []Variable `json:"variables" required:"true" nullable:"false" description:"The dashboard variables, each a ListVariable or TextVariable envelope, referenced in queries as $name. Names must be unique. Use [] for none."`
Panels map[string]*Panel `json:"panels" required:"true" nullable:"false" description:"The panels keyed by panel id. An id has up to 75 characters from letters, digits, dot, underscore and hyphen. A panel is shown only where a layout item references it as #/spec/panels/<id>. Use {} for none."`
Layouts []Layout `json:"layouts" required:"true" nullable:"false" description:"The Grid layouts that place panels, at most 500. Each panel can be placed at most once. Use [] for none."`
Duration common.DurationString `json:"duration" description:"The default time range, as a Prometheus duration such as 1h or 7d. Stored, but not applied by the UI."`
RefreshInterval common.DurationString `json:"refreshInterval" description:"The auto refresh interval, as a Prometheus duration such as 30s. Stored, but not applied by the UI."`
Links []Link `json:"links,omitzero" description:"External links stored with the dashboard. Not shown by the UI."`
}
// ══════════════════════════════════════════════

View File

@@ -12,7 +12,7 @@ import (
// PatchableDashboardV2 is an RFC 6902 patch request.
type PatchableDashboardV2 struct {
// Ops shapes the OpenAPI schema; tagged so swaggest reflects it.
Ops []JSONPatchOperation `json:"ops"`
Ops []JSONPatchOperation `json:"ops" description:"The RFC 6902 operations, applied in order against the dashboard's postable shape."`
// patch holds the decoded payload, set by UnmarshalJSON.
patch jsonpatch.Patch
}
@@ -29,7 +29,7 @@ func (PatchableDashboardV2) PrepareJSONSchema(s *jsonschema.Schema) error {
}
type JSONPatchOperation struct {
Op PatchOp `json:"op" required:"true"`
Op PatchOp `json:"op" required:"true" description:"The RFC 6902 verb: add, remove, replace, move, copy or test. remove on a missing path is a no-op and add creates missing parents."`
Path string `json:"path" required:"true" description:"JSON Pointer (RFC 6901) into the dashboard's postable shape — e.g. /spec/display/name, /spec/panels/<id>, /spec/panels/<id>/spec/queries/0, /tags/-."`
// `value` is required for add/replace/test.
Value any `json:"value" description:"Value to add/replace/test against. The expected type depends on the path. Common shapes (see referenced schemas for the exact field set): /spec/panels/<id> takes a DashboardtypesPanel; /spec/panels/<id>/spec/queries/N (or /-) takes a DashboardtypesQuery; /spec/variables/N takes a DashboardtypesVariable; /spec/layouts/N takes a DashboardtypesLayout; /tags/N (or /-) takes a TagtypesPostableTag; /spec/display/name and other leaf string fields take a string. Required for add/replace/test; ignored for remove/move/copy."`

View File

@@ -19,8 +19,8 @@ import (
// ══════════════════════════════════════════════
type PanelPlugin struct {
Kind PanelPluginKind `json:"kind" required:"true"`
Spec any `json:"spec" required:"true"`
Kind PanelPluginKind `json:"kind" required:"true" description:"The panel kind. It decides the shape of spec and which query kinds the panel accepts."`
Spec any `json:"spec" required:"true" description:"The options for this panel kind. {} accepts every default."`
}
// PrepareJSONSchema marks the envelope with x-signoz-discriminator;
@@ -75,8 +75,8 @@ func (PanelPlugin) JSONSchemaOneOf() []any {
}
type PanelPluginVariant[S any] struct {
Kind string `json:"kind" required:"true"`
Spec S `json:"spec" required:"true"`
Kind string `json:"kind" required:"true" description:"The panel kind."`
Spec S `json:"spec" required:"true" description:"The options for this panel kind. {} accepts every default."`
}
func (v PanelPluginVariant[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
@@ -88,8 +88,8 @@ func (v PanelPluginVariant[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
// ══════════════════════════════════════════════
type QueryPlugin struct {
Kind QueryPluginKind `json:"kind" required:"true"`
Spec any `json:"spec" required:"true"`
Kind QueryPluginKind `json:"kind" required:"true" description:"The query kind. It decides the shape of spec."`
Spec any `json:"spec" required:"true" description:"The kind-specific query spec. A builder query's shape depends on its signal."`
}
func (QueryPlugin) PrepareJSONSchema(s *jsonschema.Schema) error {
@@ -168,8 +168,8 @@ func wrapEnvelope(queryType qb.QueryType, spec any) qb.CompositeQuery {
}
type QueryPluginVariant[S any] struct {
Kind string `json:"kind" required:"true"`
Spec S `json:"spec" required:"true"`
Kind string `json:"kind" required:"true" description:"The query kind."`
Spec S `json:"spec" required:"true" description:"The kind-specific query spec."`
}
func (v QueryPluginVariant[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
@@ -181,8 +181,8 @@ func (v QueryPluginVariant[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
// ══════════════════════════════════════════════
type VariablePlugin struct {
Kind VariablePluginKind `json:"kind" required:"true"`
Spec any `json:"spec" required:"true"`
Kind VariablePluginKind `json:"kind" required:"true" description:"The value source kind. It decides the shape of spec."`
Spec any `json:"spec" required:"true" description:"The kind-specific value source spec."`
}
func (VariablePlugin) PrepareJSONSchema(s *jsonschema.Schema) error {
@@ -220,8 +220,8 @@ func (VariablePlugin) JSONSchemaOneOf() []any {
}
type VariablePluginVariant[S any] struct {
Kind string `json:"kind" required:"true"`
Spec S `json:"spec" required:"true"`
Kind string `json:"kind" required:"true" description:"The value source kind."`
Spec S `json:"spec" required:"true" description:"The kind-specific value source spec."`
}
func (v VariablePluginVariant[S]) PrepareJSONSchema(s *jsonschema.Schema) error {

View File

@@ -12,14 +12,14 @@ import (
// PublicWidgetQueryRangeParams are the query params of the public panel query-range endpoint.
type PublicWidgetQueryRangeParams struct {
StartTime string `query:"startTime" required:"false"`
EndTime string `query:"endTime" required:"false"`
StartTime string `query:"startTime" required:"false" description:"Start of the time range as a Unix timestamp in milliseconds. Honored only when the public dashboard has timeRangeEnabled."`
EndTime string `query:"endTime" required:"false" description:"End of the time range as a Unix timestamp in milliseconds. Honored only when the public dashboard has timeRangeEnabled."`
}
// GettablePublicDashboardDataV2 is the anonymous-facing payload of a v2 dashboard.
type GettablePublicDashboardDataV2 struct {
Dashboard *GettableDashboardV2 `json:"dashboard"`
PublicDashboard *GettablePublicDasbhboard `json:"publicDashboard"`
Dashboard *GettableDashboardV2 `json:"dashboard" description:"The dashboard with panel and variable queries redacted."`
PublicDashboard *GettablePublicDasbhboard `json:"publicDashboard" description:"The sharing settings."`
}
// NewPublicDashboardDataFromDashboardV2 builds the anonymous v2 payload: panel queries

View File

@@ -25,10 +25,10 @@ const MaxDisplayNameLen = 128
const MaxLayoutTitleLen = 256
type Display struct {
Name string `json:"name" required:"true"`
Name string `json:"name" required:"true" maxLength:"128" description:"The human-readable title, at most 128 characters."`
// Description always serializes ("" included) so a create -> GET round-trip
// preserves what a typed client sent; omitempty would drop an explicit "".
Description string `json:"description"`
Description string `json:"description" description:"A free-text description."`
}
func (d Display) Validate(label, path string) error {
@@ -43,8 +43,8 @@ func (d Display) Validate(label, path string) error {
// ══════════════════════════════════════════════
type Panel struct {
Kind PanelKind `json:"kind" required:"true"`
Spec PanelSpec `json:"spec" required:"true"`
Kind PanelKind `json:"kind" required:"true" description:"The panel envelope kind. Always Panel."`
Spec PanelSpec `json:"spec" required:"true" description:"The panel content: display, plugin, queries and links."`
}
// PanelKind is the panel envelope discriminator. Perses leaves it a free
@@ -69,10 +69,10 @@ func (k *PanelKind) UnmarshalJSON(data []byte) error {
}
type PanelSpec struct {
Display Display `json:"display" required:"true"`
Plugin PanelPlugin `json:"plugin" required:"true"`
Queries []Query `json:"queries" required:"true" nullable:"false"`
Links []Link `json:"links,omitzero"`
Display Display `json:"display" required:"true" description:"The panel title and description."`
Plugin PanelPlugin `json:"plugin" required:"true" description:"The visualization: kind is one of signoz/TimeSeriesPanel, signoz/BarChartPanel, signoz/AreaChartPanel, signoz/NumberPanel, signoz/PieChartPanel, signoz/TablePanel, signoz/HistogramPanel, signoz/ListPanel, signoz/TextPanel or signoz/HeatmapPanel, and spec holds that kind's options ({} accepts every default)."`
Queries []Query `json:"queries" required:"true" nullable:"false" description:"The queries the panel runs. Use exactly one entry for a data panel and [] for signoz/TextPanel. To run several named queries or formulas, use one entry whose spec.plugin.kind is signoz/CompositeQuery and put the constituent queries in its plugin spec."`
Links []Link `json:"links,omitzero" description:"Context links offered in the data point drilldown menu of the panel, with dashboard variables resolved in the URL."`
}
// Link replicates dashboard.Link (Perses) so its zero-valued fields survive the
@@ -80,11 +80,11 @@ type PanelSpec struct {
// omitempty, which drops "" and false; here every field always serializes so a
// typed client reads back exactly what it sent.
type Link struct {
Name string `json:"name"`
URL string `json:"url"`
Tooltip string `json:"tooltip"`
RenderVariables bool `json:"renderVariables"`
TargetBlank bool `json:"targetBlank"`
Name string `json:"name" description:"The link label."`
URL string `json:"url" description:"The link target. May reference dashboard variables as {{name}} when renderVariables is true."`
Tooltip string `json:"tooltip" description:"Hover text for the link. Stored, but not shown by the UI."`
RenderVariables bool `json:"renderVariables" description:"Whether dashboard variable values are substituted into url before opening it."`
TargetBlank bool `json:"targetBlank" description:"Whether the link opens in a new tab."`
}
// ══════════════════════════════════════════════
@@ -92,13 +92,13 @@ type Link struct {
// ══════════════════════════════════════════════
type Query struct {
Kind qb.RequestType `json:"kind" required:"true"`
Spec QuerySpec `json:"spec" required:"true"`
Kind qb.RequestType `json:"kind" required:"true" description:"The result shape the panel requests: time_series for time series, bar, area and histogram panels, scalar for number, table and pie panels, raw for list panels, heatmap for heatmap panels, trace for trace panels."`
Spec QuerySpec `json:"spec" required:"true" description:"The query content: an optional name and the query plugin."`
}
type QuerySpec struct {
Name string `json:"name,omitempty"`
Plugin QueryPlugin `json:"plugin" required:"true"`
Name string `json:"name,omitempty" description:"An optional label for the query. The name queries refer to each other by lives in the plugin spec."`
Plugin QueryPlugin `json:"plugin" required:"true" description:"The query: kind is one of signoz/BuilderQuery, signoz/AIBuilderQuery (builder query over AI observability traces), signoz/CompositeQuery (several named queries and formulas), signoz/Formula, signoz/PromQLQuery, signoz/ClickHouseSQL or signoz/TraceOperator, and spec holds that kind's query. Pie and table panels reject signoz/PromQLQuery; heatmap panels reject signoz/AIBuilderQuery and signoz/TraceOperator; list panels accept only signoz/BuilderQuery and signoz/AIBuilderQuery."`
}
// ══════════════════════════════════════════════
@@ -109,8 +109,8 @@ type QuerySpec struct {
// *TextVariableSpec by UnmarshalJSON based on Kind. The schema is a
// discriminated oneOf (see JSONSchemaOneOf).
type Variable struct {
Kind variable.Kind `json:"kind" required:"true"`
Spec any `json:"spec" required:"true"`
Kind variable.Kind `json:"kind" required:"true" description:"The variable kind: ListVariable (values from a plugin) or TextVariable (a free-text value)."`
Spec any `json:"spec" required:"true" description:"The kind-specific variable spec."`
}
func (Variable) PrepareJSONSchema(s *jsonschema.Schema) error {
@@ -154,8 +154,8 @@ func (Variable) JSONSchemaOneOf() []any {
}
type VariableEnvelope[S any] struct {
Kind string `json:"kind" required:"true"`
Spec S `json:"spec" required:"true"`
Kind string `json:"kind" required:"true" description:"The variable kind."`
Spec S `json:"spec" required:"true" description:"The kind-specific variable spec."`
}
func (v VariableEnvelope[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
@@ -165,15 +165,15 @@ func (v VariableEnvelope[S]) PrepareJSONSchema(s *jsonschema.Schema) error {
// ListVariableSpec mirrors dashboard.ListVariableSpec (variable.ListSpec
// fields + Name) but with a typed VariablePlugin replacing common.Plugin.
type ListVariableSpec struct {
Display Display `json:"display" required:"true"`
DefaultValue *VariableDefaultValue `json:"defaultValue,omitempty"`
AllowAllValue bool `json:"allowAllValue"`
AllowMultiple bool `json:"allowMultiple"`
CustomAllValue string `json:"customAllValue"`
CapturingRegexp string `json:"capturingRegexp"`
Sort ListVariableSpecSort `json:"sort"`
Plugin VariablePlugin `json:"plugin"`
Name string `json:"name" required:"true" minLength:"1"`
Display Display `json:"display" required:"true" description:"The label shown for the variable control."`
DefaultValue *VariableDefaultValue `json:"defaultValue,omitempty" description:"The initially selected value: a string, or a list of strings when allowMultiple is true."`
AllowAllValue bool `json:"allowAllValue" description:"Whether an all option is offered. Requires allowMultiple."`
AllowMultiple bool `json:"allowMultiple" description:"Whether several values can be selected at once."`
CustomAllValue string `json:"customAllValue" description:"The value meant to replace every value when all is selected. Requires allowAllValue. Stored, but currently ignored by the UI."`
CapturingRegexp string `json:"capturingRegexp" description:"A regular expression meant to keep only matching values, reduced to the capture group when one is present. Stored, but not applied by the UI."`
Sort ListVariableSpecSort `json:"sort" description:"How the values are ordered: none (default), alphabetical-asc, alphabetical-desc, numerical-asc, numerical-desc, alphabetical-ci-asc or alphabetical-ci-desc."`
Plugin VariablePlugin `json:"plugin" description:"Where the values come from: kind is signoz/DynamicVariable (observed attribute values), signoz/QueryVariable (a ClickHouse query) or signoz/CustomVariable (a fixed list), and spec holds that kind's settings."`
Name string `json:"name" required:"true" minLength:"1" description:"The handle queries reference as $name. Up to 75 characters from letters, digits, dot, underscore and hyphen, not all digits, unique within the dashboard."`
}
// VariableDefaultValue is a list variable's defaultValue: the string | []string
@@ -294,10 +294,10 @@ func (s *ListVariableSpecSort) UnmarshalJSON(data []byte) error {
// TextVariableSpec replicates dashboard.TextVariableSpec so name can carry the
// required/non-empty schema tags perses leaves off.
type TextVariableSpec struct {
Display Display `json:"display" required:"true"`
Value string `json:"value" required:"true"`
Constant bool `json:"constant"`
Name string `json:"name" required:"true" minLength:"1"`
Display Display `json:"display" required:"true" description:"The label shown for the variable control."`
Value string `json:"value" required:"true" description:"The default text. Must be non-empty when constant is true."`
Constant bool `json:"constant" description:"Whether the value is meant to be fixed. Stored, but not applied by the UI."`
Name string `json:"name" required:"true" minLength:"1" description:"The handle queries reference as $name. Up to 75 characters from letters, digits, dot, underscore and hyphen, not all digits, unique within the dashboard."`
}
// validate mirrors perses TextVariableSpec validation. path is the JSON path to
@@ -328,8 +328,8 @@ func (s *TextVariableSpec) validate(path string) error {
// based on Kind. No plugin is involved, so we reuse the Perses spec types as
// leaf imports.
type Layout struct {
Kind dashboard.LayoutKind `json:"kind" required:"true"`
Spec any `json:"spec" required:"true"`
Kind dashboard.LayoutKind `json:"kind" required:"true" description:"The layout kind. Always Grid."`
Spec any `json:"spec" required:"true" description:"The grid section: an optional display title and up to 100 items that place panels. Each item has a content.$ref of #/spec/panels/<id> plus x, y, width and height on a 12-column grid: x and y at least 0, width and height at least 1, x plus width at most 12, and no two items overlapping."`
}
// layoutSpecs is the layout sum type factory. Perses only defines
@@ -419,8 +419,8 @@ func (Layout) JSONSchemaOneOf() []any {
}
type LayoutEnvelope[S any] struct {
Kind string `json:"kind" required:"true"`
Spec S `json:"spec" required:"true"`
Kind string `json:"kind" required:"true" description:"The layout kind."`
Spec S `json:"spec" required:"true" description:"The grid section: an optional display title and up to 100 items that place panels. Each item has a content.$ref of #/spec/panels/<id> plus x, y, width and height on a 12-column grid: x and y at least 0, width and height at least 1, x plus width at most 12, and no two items overlapping."`
}
func (v LayoutEnvelope[S]) PrepareJSONSchema(s *jsonschema.Schema) error {

View File

@@ -29,10 +29,9 @@ func (VariablePluginKind) Enum() []any {
}
type DynamicVariableSpec struct {
// Name is the name of the attribute being fetched dynamically from the
// signal. This could be extended to a richer selector in the future.
Name string `json:"name" validate:"required" required:"true"`
Signal DynamicVariableSignal `json:"signal" required:"true" nullable:"false"`
// This could be extended to a richer selector in the future.
Name string `json:"name" validate:"required" required:"true" description:"The attribute key whose observed values populate the variable, such as service.name."`
Signal DynamicVariableSignal `json:"signal" required:"true" nullable:"false" description:"The signal the values are read from: traces, logs, metrics or all (default)."`
}
// DynamicVariableSignal is the telemetry signal a dynamic variable draws its
@@ -78,11 +77,11 @@ func (s *DynamicVariableSignal) UnmarshalJSON(data []byte) error {
}
type QueryVariableSpec struct {
QueryValue string `json:"queryValue" validate:"required" required:"true"`
QueryValue string `json:"queryValue" validate:"required" required:"true" description:"A ClickHouse SQL query returning one column of selectable values; every returned column is offered."`
}
type CustomVariableSpec struct {
CustomValue string `json:"customValue" validate:"required" required:"true"`
CustomValue string `json:"customValue" validate:"required" required:"true" description:"The fixed values as a comma-separated list, such as 1,10,100."`
}
// ══════════════════════════════════════════════
@@ -208,37 +207,37 @@ func (k PanelPluginKind) rendersWithoutQuery() bool {
}
type TimeSeriesPanelSpec struct {
Visualization TimeSeriesVisualization `json:"visualization"`
Formatting PanelFormatting `json:"formatting"`
ChartAppearance TimeSeriesChartAppearance `json:"chartAppearance"`
Axes Axes `json:"axes"`
Legend Legend `json:"legend"`
Thresholds []ThresholdWithLabel `json:"thresholds" validate:"dive"`
Visualization TimeSeriesVisualization `json:"visualization" description:"Time range and gap handling."`
Formatting PanelFormatting `json:"formatting" description:"Unit and decimal precision of the values."`
ChartAppearance TimeSeriesChartAppearance `json:"chartAppearance" description:"Line drawing options."`
Axes Axes `json:"axes" description:"Y axis bounds and scale."`
Legend Legend `json:"legend" description:"Legend placement and series colors."`
Thresholds []ThresholdWithLabel `json:"thresholds" validate:"dive" description:"Horizontal reference lines drawn at fixed values."`
}
type TimeSeriesChartAppearance struct {
LineInterpolation LineInterpolation `json:"lineInterpolation"`
ShowPoints bool `json:"showPoints"`
LineStyle LineStyle `json:"lineStyle"`
FillMode FillMode `json:"fillMode"`
SpanGaps SpanGaps `json:"spanGaps"`
LineInterpolation LineInterpolation `json:"lineInterpolation" description:"How points are joined: linear, spline (default), step_after or step_before."`
ShowPoints bool `json:"showPoints" description:"Whether each data point is marked on the line."`
LineStyle LineStyle `json:"lineStyle" description:"The line stroke: solid (default) or dashed."`
FillMode FillMode `json:"fillMode" description:"The fill under each line: none (default), solid or gradient."`
SpanGaps SpanGaps `json:"spanGaps" description:"Whether lines connect across missing points."`
}
type BarChartPanelSpec struct {
Visualization BarChartVisualization `json:"visualization"`
Formatting PanelFormatting `json:"formatting"`
Axes Axes `json:"axes"`
Legend Legend `json:"legend"`
Thresholds []ThresholdWithLabel `json:"thresholds" validate:"dive"`
Visualization BarChartVisualization `json:"visualization" description:"Time range, gap handling and stacking."`
Formatting PanelFormatting `json:"formatting" description:"Unit and decimal precision of the values."`
Axes Axes `json:"axes" description:"Y axis bounds and scale."`
Legend Legend `json:"legend" description:"Legend placement and series colors."`
Thresholds []ThresholdWithLabel `json:"thresholds" validate:"dive" description:"Horizontal reference lines drawn at fixed values."`
}
type AreaChartPanelSpec struct {
Visualization AreaChartVisualization `json:"visualization"`
Formatting PanelFormatting `json:"formatting"`
ChartAppearance AreaChartAppearance `json:"chartAppearance"`
Axes Axes `json:"axes"`
Legend Legend `json:"legend"`
Thresholds []ThresholdWithLabel `json:"thresholds" validate:"dive"`
Visualization AreaChartVisualization `json:"visualization" description:"Time range, gap handling and stacking."`
Formatting PanelFormatting `json:"formatting" description:"Unit and decimal precision of the values."`
ChartAppearance AreaChartAppearance `json:"chartAppearance" description:"Line and fill drawing options."`
Axes Axes `json:"axes" description:"Y axis bounds and scale."`
Legend Legend `json:"legend" description:"Legend placement and series colors."`
Thresholds []ThresholdWithLabel `json:"thresholds" validate:"dive" description:"Horizontal reference lines drawn at fixed values."`
}
// AreaChartAppearance repeats the line-drawing fields rather than embedding
@@ -246,83 +245,83 @@ type AreaChartPanelSpec struct {
// a duplicated json tag across an embed boundary is resolved by depth, which the
// schema reflector does not model.
type AreaChartAppearance struct {
LineInterpolation LineInterpolation `json:"lineInterpolation"`
ShowPoints bool `json:"showPoints"`
LineStyle LineStyle `json:"lineStyle"`
FillMode AreaFillMode `json:"fillMode"`
LineInterpolation LineInterpolation `json:"lineInterpolation" description:"How points are joined: linear, spline (default), step_after or step_before."`
ShowPoints bool `json:"showPoints" description:"Whether each data point is marked on the line."`
LineStyle LineStyle `json:"lineStyle" description:"The line stroke: solid (default) or dashed."`
FillMode AreaFillMode `json:"fillMode" description:"The fill under each line: solid (default) or gradient."`
// FillOpacity is a pointer so an omitted field resolves to the kind default at
// render time; a plain value would make the Go zero value a transparent fill.
FillOpacity *FillOpacity `json:"fillOpacity"`
SpanGaps SpanGaps `json:"spanGaps"`
FillOpacity *FillOpacity `json:"fillOpacity" description:"The fill opacity from 0 (transparent) to 1 (opaque). Null uses the chart default."`
SpanGaps SpanGaps `json:"spanGaps" description:"Whether lines connect across missing points."`
}
type NumberPanelSpec struct {
Visualization BasicVisualization `json:"visualization"`
Formatting PanelFormatting `json:"formatting"`
Thresholds []ComparisonThreshold `json:"thresholds" validate:"dive"`
Visualization BasicVisualization `json:"visualization" description:"The time range the value is computed over."`
Formatting PanelFormatting `json:"formatting" description:"Unit and decimal precision of the value."`
Thresholds []ComparisonThreshold `json:"thresholds" validate:"dive" description:"Rules that recolor the value when it compares to a threshold as specified."`
}
type PieChartPanelSpec struct {
Visualization BasicVisualization `json:"visualization"`
Formatting PanelFormatting `json:"formatting"`
Legend Legend `json:"legend"`
Visualization BasicVisualization `json:"visualization" description:"The time range the slices are computed over."`
Formatting PanelFormatting `json:"formatting" description:"Unit and decimal precision of the slice values."`
Legend Legend `json:"legend" description:"Legend placement and slice colors."`
}
type TablePanelSpec struct {
Visualization BasicVisualization `json:"visualization"`
Formatting TableFormatting `json:"formatting"`
Thresholds []TableThreshold `json:"thresholds" validate:"dive"`
Visualization BasicVisualization `json:"visualization" description:"The time range the rows are computed over."`
Formatting TableFormatting `json:"formatting" description:"Per-column units and decimal precision."`
Thresholds []TableThreshold `json:"thresholds" validate:"dive" description:"Rules that recolor a cell when its column value compares to a threshold as specified."`
}
type HistogramPanelSpec struct {
HistogramBuckets HistogramBuckets `json:"histogramBuckets"`
Legend Legend `json:"legend"`
HistogramBuckets HistogramBuckets `json:"histogramBuckets" description:"How values are grouped into buckets."`
Legend Legend `json:"legend" description:"Legend placement and series colors."`
}
type HistogramBuckets struct {
BucketCount *float64 `json:"bucketCount"`
BucketWidth *float64 `json:"bucketWidth"`
MergeAllActiveQueries bool `json:"mergeAllActiveQueries"`
BucketCount *float64 `json:"bucketCount" description:"The approximate target number of buckets. Ignored when bucketWidth is positive. Null uses the chart default."`
BucketWidth *float64 `json:"bucketWidth" description:"The width of each bucket in the value unit. A positive value takes precedence over bucketCount; null or 0 lets the chart choose."`
MergeAllActiveQueries bool `json:"mergeAllActiveQueries" description:"Whether every series is combined into a single histogram instead of one per series."`
}
type ListPanelSpec struct {
SelectFields []telemetrytypes.TelemetryFieldKey `json:"selectFields,omitzero" validate:"dive"`
SelectFields []telemetrytypes.TelemetryFieldKey `json:"selectFields,omitzero" validate:"dive" description:"The columns shown for each row, as field keys with name and optional fieldContext, fieldDataType and signal. Empty shows the default columns."`
}
type HeatmapPanelSpec struct {
Visualization BasicVisualization `json:"visualization"`
Formatting PanelFormatting `json:"formatting"`
Axes HeatmapAxes `json:"axes"`
Legend Legend `json:"legend"`
ChartAppearance HeatmapChartAppearance `json:"chartAppearance"`
Visualization BasicVisualization `json:"visualization" description:"The time range the heatmap covers."`
Formatting PanelFormatting `json:"formatting" description:"Unit and decimal precision of the bucket bounds."`
Axes HeatmapAxes `json:"axes" description:"The Y axis scale."`
Legend Legend `json:"legend" description:"Legend placement and series colors."`
ChartAppearance HeatmapChartAppearance `json:"chartAppearance" description:"How bucket counts map to colors."`
}
// 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 {
Y HeatmapAxis `json:"y"`
Y HeatmapAxis `json:"y" description:"The bucket axis."`
}
type HeatmapAxis struct {
Scale HeatmapYScale `json:"scale"`
Scale HeatmapYScale `json:"scale" description:"How bucket bounds are spaced on the axis: auto (default), linear, log or symlog."`
}
type HeatmapChartAppearance struct {
Colors HeatmapColors `json:"colors"`
Colors HeatmapColors `json:"colors" description:"The color scale applied to bucket counts."`
}
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"`
Mode HeatmapColorMode `json:"mode" description:"How counts are colored: palette (default) picks a hue from palette, opacity varies the opacity of fill."`
Palette HeatmapPalette `json:"palette" description:"The named color ramp used in palette mode, such as lava (default), ice or ember."`
Scale HeatmapColorScale `json:"scale" description:"How counts map onto the color range: log (default), sqrt or linear."`
Steps int `json:"steps" validate:"omitempty,min=2,max=128" description:"The number of distinct color steps, 2 to 128. 0 uses the chart default."`
// 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"`
MinCount *float64 `json:"minCount" description:"The count mapped to the lowest color. Null uses 0. Must not exceed maxCount."`
MaxCount *float64 `json:"maxCount" description:"The count mapped to the highest color. Null uses the highest count in the grid."`
// Fill applies in opacity mode; empty means the selected group's legend colour.
Fill string `json:"fill"`
Fill string `json:"fill" description:"The hex color varied in opacity mode. Empty uses the series legend color."`
}
func (c *HeatmapColors) UnmarshalJSON(data []byte) error {
@@ -344,16 +343,16 @@ func (c HeatmapColors) validate() error {
}
type TextPanelSpec struct {
Mode TextMode `json:"mode"`
Text string `json:"text"`
Presentation TextPresentation `json:"presentation"`
HeaderOptions HeaderOptions `json:"headerOptions"`
Mode TextMode `json:"mode" description:"How text is interpreted. Only markdown is supported."`
Text string `json:"text" description:"The content to render."`
Presentation TextPresentation `json:"presentation" description:"Alignment and background of the content."`
HeaderOptions HeaderOptions `json:"headerOptions" description:"Whether the panel header is shown."`
}
type TextPresentation struct {
TextAlign TextAlign `json:"textAlign"`
VerticalAlign VerticalAlign `json:"verticalAlign"`
Background *string `json:"background,omitempty" validate:"omitempty,hexcolor"`
TextAlign TextAlign `json:"textAlign" description:"Horizontal alignment: left (default), center or right."`
VerticalAlign VerticalAlign `json:"verticalAlign" description:"Vertical alignment: top (default), center or bottom."`
Background *string `json:"background,omitempty" validate:"omitempty,hexcolor" description:"A hex background color such as #1f2937. Omit for the panel default."`
}
// ══════════════════════════════════════════════
@@ -361,79 +360,79 @@ type TextPresentation struct {
// ══════════════════════════════════════════════
type Axes struct {
SoftMin *float64 `json:"softMin"`
SoftMax *float64 `json:"softMax"`
IsLogScale bool `json:"isLogScale"`
SoftMin *float64 `json:"softMin" description:"The lowest value the Y axis always shows; data below it still extends the axis. Null fits the data."`
SoftMax *float64 `json:"softMax" description:"The highest value the Y axis always shows; data above it still extends the axis. Null fits the data."`
IsLogScale bool `json:"isLogScale" description:"Whether the Y axis uses a logarithmic scale."`
}
// HeaderOptions controls the panel card's header strip — the title/description
// row above the panel content. Phrased as hide so the zero value shows the
// header, matching every other panel kind.
type HeaderOptions struct {
Hide bool `json:"hide"`
Hide bool `json:"hide" description:"Whether the title and description row above the content is hidden."`
}
type BasicVisualization struct {
TimePreference TimePreference `json:"timePreference"`
TimePreference TimePreference `json:"timePreference" description:"The time range the panel queries: global_time (default) follows the dashboard range; last_5_min, last_15_min, last_30_min, last_1_hr, last_6_hr, last_1_day, last_3_days, last_1_week or last_1_month pin a fixed window."`
}
type TimeSeriesVisualization struct {
BasicVisualization
FillSpans bool `json:"fillSpans"`
FillSpans bool `json:"fillSpans" description:"Whether intervals with no data are drawn as zero instead of a gap."`
}
type BarChartVisualization struct {
BasicVisualization
FillSpans bool `json:"fillSpans"`
StackedBarChart bool `json:"stackedBarChart"`
FillSpans bool `json:"fillSpans" description:"Whether intervals with no data are drawn as zero instead of a gap."`
StackedBarChart bool `json:"stackedBarChart" description:"Whether the series are stacked on top of each other."`
}
type AreaChartVisualization struct {
BasicVisualization
FillSpans bool `json:"fillSpans"`
Stack StackMode `json:"stack"`
FillSpans bool `json:"fillSpans" description:"Whether intervals with no data are drawn as zero instead of a gap."`
Stack StackMode `json:"stack" description:"How the areas combine: none (default) overlays them, normal stacks the values, percent stacks them as shares of the total."`
}
type PanelFormatting struct {
Unit string `json:"unit"`
DecimalPrecision PrecisionOption `json:"decimalPrecision"`
Unit string `json:"unit" description:"The display unit of the values, such as By, ms, s, percent or percentunit. Use percent for values already expressed as percentages (95 displays as 95%) and percentunit for fractions (0.95 displays as 95%). Empty shows plain numbers."`
DecimalPrecision PrecisionOption `json:"decimalPrecision" description:"Decimal places shown: 0, 1, 2 (default), 3, 4 or full."`
}
type TableFormatting struct {
ColumnUnits map[string]string `json:"columnUnits"`
DecimalPrecision PrecisionOption `json:"decimalPrecision"`
ColumnUnits map[string]string `json:"columnUnits" description:"Unit id per value column, keyed by the column key: the query name for a builder query with one aggregation (A), the query name dotted with the aggregation expression for several (A.count()), or the SQL alias for a ClickHouse SQL column. Aggregation aliases are display labels, not keys."`
DecimalPrecision PrecisionOption `json:"decimalPrecision" description:"Decimal places shown: 0, 1, 2 (default), 3, 4 or full."`
}
type Legend struct {
Position LegendPosition `json:"position"`
Mode LegendMode `json:"mode"`
CustomColors map[string]string `json:"customColors"`
Position LegendPosition `json:"position" description:"Where the legend sits: bottom (default) or right."`
Mode LegendMode `json:"mode" description:"The legend layout. Only list is supported."`
CustomColors map[string]string `json:"customColors" description:"Hex colors keyed by series label, overriding the automatic palette."`
}
type ThresholdWithLabel struct {
// Value is always present in the schema (required:"true"), but 0 is a legitimate
// threshold, so it drops validate:"required" — go-playground's required treats a
// zero float as unset and would wrongly reject value: 0.
Value float64 `json:"value" required:"true"`
Unit string `json:"unit"`
Color string `json:"color" validate:"required" required:"true"`
Label string `json:"label"`
Value float64 `json:"value" required:"true" description:"The value the line is drawn at, in unit."`
Unit string `json:"unit" description:"The unit id of value, such as ms or percent. Empty means the panel's unit, or the column's unit for a table threshold. Prefer the panel's unit; a different unit works only when it converts to it, such as s against a panel in ms."`
Color string `json:"color" validate:"required" required:"true" description:"The CSS color of the line, for example #F1575F, which matches the SigNoz red preset; CSS named colors such as red also work."`
Label string `json:"label" description:"The caption shown on the line."`
}
type ComparisonThreshold struct {
// Value is always present in the schema (required:"true"), but 0 is a legitimate
// threshold, so it drops validate:"required" — go-playground's required treats a
// zero float as unset and would wrongly reject value: 0.
Value float64 `json:"value" required:"true"`
Operator ComparisonOperator `json:"operator"`
Unit string `json:"unit"`
Color string `json:"color" validate:"required" required:"true"`
Format ThresholdFormat `json:"format"`
Value float64 `json:"value" required:"true" description:"The value compared against, in unit."`
Operator ComparisonOperator `json:"operator" description:"The comparison that activates the threshold: above (default), below, above_or_equal, below_or_equal, equal or not_equal."`
Unit string `json:"unit" description:"The unit id of value, such as ms or percent. Empty means the panel's unit, or the column's unit for a table threshold. Prefer the panel's unit; a different unit works only when it converts to it, such as s against a panel in ms."`
Color string `json:"color" validate:"required" required:"true" description:"The CSS color applied to the text or background, per format, while the threshold is active. For example #F1575F matches the SigNoz red preset; CSS named colors such as red also work."`
Format ThresholdFormat `json:"format" description:"What the color is applied to: text (default) or background."`
}
type TableThreshold struct {
ComparisonThreshold
ColumnName string `json:"columnName" validate:"required" required:"true"`
ColumnName string `json:"columnName" validate:"required" required:"true" description:"The key of the value column the rule applies to: the query name for a builder query with one aggregation (A), the query name dotted with the aggregation expression for several (A.count()), or the SQL alias for a ClickHouse SQL column. Group-by columns are never colored, and aggregation aliases are display labels, not keys."`
}
// ══════════════════════════════════════════════
@@ -856,7 +855,7 @@ func (o *FillOpacity) UnmarshalJSON(data []byte) error {
type SpanGaps struct {
FillOnlyBelow bool `json:"fillOnlyBelow" description:"Controls whether lines connect across null values. When false (default), all gaps are connected. When true, only gaps smaller than fillLessThan are connected."`
FillLessThan string `json:"fillLessThan" description:"The maximum gap size to connect when fillOnlyBelow is true. Gaps larger than this duration are left disconnected."`
FillLessThan string `json:"fillLessThan" description:"The largest gap still connected when fillOnlyBelow is true; longer gaps are left disconnected. Required in that case, as a positive Prometheus duration such as 30s, 5m or 1d."`
}
func (sg *SpanGaps) UnmarshalJSON(data []byte) error {

View File

@@ -32,25 +32,25 @@ type PublicDashboard struct {
types.Identifiable
types.TimeAuditable
TimeRangeEnabled bool `json:"timeRangeEnabled"`
DefaultTimeRange string `json:"defaultTimeRange"`
DashboardID valuer.UUID `json:"dashboardId"`
TimeRangeEnabled bool `json:"timeRangeEnabled" description:"Whether anonymous viewers may change the time range."`
DefaultTimeRange string `json:"defaultTimeRange" description:"The time range shown to anonymous viewers by default, as a Go duration such as 30m, 1h or 24h. Accepted units are ns, us, ms, s, m and h; write a day as 24h."`
DashboardID valuer.UUID `json:"dashboardId" description:"The id of the dashboard being shared."`
}
type GettablePublicDasbhboard struct {
TimeRangeEnabled bool `json:"timeRangeEnabled"`
DefaultTimeRange string `json:"defaultTimeRange"`
PublicPath string `json:"publicPath"`
TimeRangeEnabled bool `json:"timeRangeEnabled" description:"Whether anonymous viewers may change the time range."`
DefaultTimeRange string `json:"defaultTimeRange" description:"The time range shown to anonymous viewers by default, as a Go duration such as 30m, 1h or 24h. Accepted units are ns, us, ms, s, m and h; write a day as 24h."`
PublicPath string `json:"publicPath" description:"The path segment of the public URL."`
}
type PostablePublicDashboard struct {
TimeRangeEnabled bool `json:"timeRangeEnabled"`
DefaultTimeRange string `json:"defaultTimeRange"`
TimeRangeEnabled bool `json:"timeRangeEnabled" description:"Whether anonymous viewers may change the time range."`
DefaultTimeRange string `json:"defaultTimeRange" description:"The time range shown to anonymous viewers by default, as a Go duration such as 30m, 1h or 24h. Accepted units are ns, us, ms, s, m and h; write a day as 24h."`
}
type UpdatablePublicDashboard struct {
TimeRangeEnabled bool `json:"timeRangeEnabled"`
DefaultTimeRange string `json:"defaultTimeRange"`
TimeRangeEnabled bool `json:"timeRangeEnabled" description:"Whether anonymous viewers may change the time range."`
DefaultTimeRange string `json:"defaultTimeRange" description:"The time range shown to anonymous viewers by default, as a Go duration such as 30m, 1h or 24h. Accepted units are ns, us, ms, s, m and h; write a day as 24h."`
}
func NewPublicDashboard(timeRangeEnabled bool, defaultTimeRange string, dashboardID valuer.UUID) *PublicDashboard {

View File

@@ -41,12 +41,12 @@ func (TreemapMode) Enum() []any {
// StatsRequest represents the payload accepted by the metrics stats endpoint.
type StatsRequest struct {
Filter *qbtypes.Filter `json:"filter,omitempty"`
Start int64 `json:"start" required:"true"`
End int64 `json:"end" required:"true"`
Limit int `json:"limit" required:"true"`
Offset int `json:"offset"`
OrderBy *qbtypes.OrderBy `json:"orderBy,omitempty"`
Filter *qbtypes.Filter `json:"filter,omitempty" description:"Query builder filter over metric attributes, for example host.name = 'web-1'. A bare word matches the metric name. Omitting it applies no restriction, but metrics whose name starts with signoz are always excluded."`
Start int64 `json:"start" required:"true" description:"Start of the time range as a Unix timestamp in milliseconds. Must be greater than 0 and less than end."`
End int64 `json:"end" required:"true" description:"End of the time range as a Unix timestamp in milliseconds. Must be greater than start."`
Limit int `json:"limit" required:"true" description:"Maximum number of metrics to return, between 1 and 5000."`
Offset int `json:"offset" description:"Number of metrics to skip for pagination. Must not be negative; defaults to 0."`
OrderBy *qbtypes.OrderBy `json:"orderBy,omitempty" description:"Sort order; key.name is timeseries or samples, direction is asc or desc. Defaults to samples descending."`
}
// Validate ensures StatsRequest contains acceptable values.
@@ -104,26 +104,26 @@ func (req *StatsRequest) UnmarshalJSON(data []byte) error {
// Stat represents the summary information returned per metric.
type Stat struct {
MetricName string `json:"metricName" required:"true"`
Description string `json:"description" required:"true"`
MetricType metrictypes.Type `json:"type" required:"true"`
MetricUnit string `json:"unit" required:"true"`
TimeSeries uint64 `json:"timeseries" required:"true"`
Samples uint64 `json:"samples" required:"true"`
MetricName string `json:"metricName" required:"true" description:"The name of the metric."`
Description string `json:"description" required:"true" description:"Human-readable description from the metric metadata; empty when none was reported."`
MetricType metrictypes.Type `json:"type" required:"true" description:"The metric type, such as gauge, sum, histogram, summary or exponentialhistogram."`
MetricUnit string `json:"unit" required:"true" description:"The unit reported in the metric metadata, for example ms or By; empty when none was reported."`
TimeSeries uint64 `json:"timeseries" required:"true" description:"Number of distinct time series active in the time range, approximate: it can include series last active shortly before start."`
Samples uint64 `json:"samples" required:"true" description:"Number of samples (data points) ingested for the metric in the requested time range."`
}
// StatsResponse represents the aggregated metrics statistics.
type StatsResponse struct {
Metrics []Stat `json:"metrics" required:"true" nullable:"true"`
Total uint64 `json:"total" required:"true"`
Metrics []Stat `json:"metrics" required:"true" nullable:"true" description:"One entry per metric on the requested page, in the requested order."`
Total uint64 `json:"total" required:"true" description:"Total number of metrics matching the filter across all pages."`
}
type MetricMetadata struct {
Description string `json:"description" required:"true"`
MetricType metrictypes.Type `json:"type" required:"true"`
MetricUnit string `json:"unit" required:"true"`
Temporality metrictypes.Temporality `json:"temporality" required:"true"`
IsMonotonic bool `json:"isMonotonic" required:"true"`
Description string `json:"description" required:"true" description:"Human-readable description of the metric; empty when none was reported."`
MetricType metrictypes.Type `json:"type" required:"true" description:"The metric type, such as gauge, sum, histogram, summary or exponentialhistogram."`
MetricUnit string `json:"unit" required:"true" description:"The unit of the metric values, for example ms or By; empty when none was reported."`
Temporality metrictypes.Temporality `json:"temporality" required:"true" description:"Aggregation temporality of the metric: cumulative, delta or unspecified."`
IsMonotonic bool `json:"isMonotonic" required:"true" description:"True when the metric is a monotonically increasing Sum (a counter)."`
}
// MarshalBinary implements cachetypes.Cacheable interface.
@@ -138,21 +138,21 @@ func (m *MetricMetadata) UnmarshalBinary(data []byte) error {
// UpdateMetricMetadataRequest represents the payload for updating metric metadata.
type UpdateMetricMetadataRequest struct {
MetricName string `json:"metricName" required:"true"`
Type metrictypes.Type `json:"type" required:"true"`
Description string `json:"description" required:"true"`
Unit string `json:"unit" required:"true"`
Temporality metrictypes.Temporality `json:"temporality" required:"true"`
IsMonotonic bool `json:"isMonotonic" required:"true"`
MetricName string `json:"metricName" required:"true" description:"The name of the metric whose metadata to replace."`
Type metrictypes.Type `json:"type" required:"true" description:"The metric type to store, such as gauge, sum, histogram, summary or exponentialhistogram."`
Description string `json:"description" required:"true" description:"Human-readable description of the metric."`
Unit string `json:"unit" required:"true" description:"The unit of the metric values, for example ms or By."`
Temporality metrictypes.Temporality `json:"temporality" required:"true" description:"Aggregation temporality to store. Required as delta or cumulative when type is sum, histogram or exponentialhistogram; gauges are stored as unspecified and summaries as cumulative whatever is sent. A non-monotonic cumulative sum is stored as a gauge."`
IsMonotonic bool `json:"isMonotonic" required:"true" description:"Whether the metric is a monotonically increasing Sum (a counter)."`
}
// TreemapRequest represents the payload for the metrics treemap endpoint.
type TreemapRequest struct {
Filter *qbtypes.Filter `json:"filter,omitempty"`
Start int64 `json:"start" required:"true"`
End int64 `json:"end" required:"true"`
Limit int `json:"limit" required:"true"`
Mode TreemapMode `json:"mode" required:"true"`
Filter *qbtypes.Filter `json:"filter,omitempty" description:"Query builder filter over metric attributes, for example host.name = 'web-1'. A bare word matches the metric name. Omitting it applies no restriction, but metrics whose name starts with signoz are always excluded."`
Start int64 `json:"start" required:"true" description:"Start of the time range as a Unix timestamp in milliseconds. Must be greater than 0 and less than end."`
End int64 `json:"end" required:"true" description:"End of the time range as a Unix timestamp in milliseconds. Must be greater than start."`
Limit int `json:"limit" required:"true" description:"Maximum number of metrics to return, between 1 and 5000, ordered by percentage descending."`
Mode TreemapMode `json:"mode" required:"true" description:"What each metric is sized by: timeseries counts distinct time series; samples counts ingested data points, ranked among a shortlist of the metrics with the most time series. Only the matching response array is populated."`
}
// Validate enforces basic constraints on TreemapRequest.
@@ -218,32 +218,32 @@ func (req *TreemapRequest) UnmarshalJSON(data []byte) error {
// TreemapEntry represents each node in the treemap response.
type TreemapEntry struct {
MetricName string `json:"metricName" required:"true"`
Percentage float64 `json:"percentage" required:"true"`
TotalValue uint64 `json:"totalValue" required:"true"`
MetricName string `json:"metricName" required:"true" description:"The name of the metric."`
Percentage float64 `json:"percentage" required:"true" description:"Share of the total across all metrics in the time range, from 0 to 100."`
TotalValue uint64 `json:"totalValue" required:"true" description:"Time series count or sample count for the metric, depending on the requested mode."`
}
// TreemapResponse is the output structure for the treemap endpoint.
type TreemapResponse struct {
TimeSeries []TreemapEntry `json:"timeseries" required:"true" nullable:"true"`
Samples []TreemapEntry `json:"samples" required:"true" nullable:"true"`
TimeSeries []TreemapEntry `json:"timeseries" required:"true" nullable:"true" description:"Entries sized by time series count; populated only when mode is timeseries."`
Samples []TreemapEntry `json:"samples" required:"true" nullable:"true" description:"Entries sized by sample count, chosen among the metrics with the most time series; populated only when mode is samples."`
}
// MetricAlert represents an alert associated with a metric.
type MetricAlert struct {
AlertName string `json:"alertName" required:"true"`
AlertID string `json:"alertId" required:"true"`
AlertName string `json:"alertName" required:"true" description:"The name of the alert rule that queries the metric."`
AlertID string `json:"alertId" required:"true" description:"The id of the alert rule."`
}
// MetricAlertsResponse represents the response for metric alerts endpoint.
type MetricAlertsResponse struct {
Alerts []MetricAlert `json:"alerts" required:"true" nullable:"true"`
Alerts []MetricAlert `json:"alerts" required:"true" nullable:"true" description:"Alert rules whose queries reference the metric."`
}
// MetricDashboardPanelsResponse is the response for the v2 metric dashboards
// endpoint: the dashboard panels that reference the metric.
type MetricDashboardPanelsResponse struct {
Dashboards []dashboardtypes.DashboardPanelRef `json:"dashboards" required:"true" nullable:"true"`
Dashboards []dashboardtypes.DashboardPanelRef `json:"dashboards" required:"true" nullable:"true" description:"One entry per dashboard panel whose query references the metric."`
}
// NewMetricDashboardPanelsResponse wraps the dashboard panels that reference a
@@ -258,10 +258,10 @@ func NewMetricDashboardPanelsResponse(refs []dashboardtypes.DashboardPanelRef) *
// MetricHighlightsResponse is the output structure for the metric highlights endpoint.
type MetricHighlightsResponse struct {
DataPoints uint64 `json:"dataPoints" required:"true"`
LastReceived uint64 `json:"lastReceived" required:"true"`
TotalTimeSeries uint64 `json:"totalTimeSeries" required:"true"`
ActiveTimeSeries uint64 `json:"activeTimeSeries" required:"true"`
DataPoints uint64 `json:"dataPoints" required:"true" description:"Number of samples ingested for the metric over retained data."`
LastReceived uint64 `json:"lastReceived" required:"true" description:"Unix timestamp in milliseconds of the most recently received sample."`
TotalTimeSeries uint64 `json:"totalTimeSeries" required:"true" description:"Number of distinct time series for the metric over retained data."`
ActiveTimeSeries uint64 `json:"activeTimeSeries" required:"true" description:"Number of distinct time series active in the last 120 minutes, approximate: a series that last reported early in that window can be missed."`
}
// MetricNameQuery represents the query parameters for endpoints that take a metric name.
@@ -308,24 +308,24 @@ func (req *MetricAttributesRequest) Validate() error {
// MetricAttribute represents a single attribute with its values and count.
type MetricAttribute struct {
Key string `json:"key" required:"true"`
Values []string `json:"values" required:"true" nullable:"true"`
ValueCount uint64 `json:"valueCount" required:"true"`
Key string `json:"key" required:"true" description:"The attribute (label) name."`
Values []string `json:"values" required:"true" nullable:"true" description:"Distinct values observed for the attribute, capped at 1000."`
ValueCount uint64 `json:"valueCount" required:"true" description:"Total number of distinct values observed, which can exceed the length of values."`
}
// MetricAttributesResponse is the output structure for the metric attributes endpoint.
type MetricAttributesResponse struct {
Attributes []MetricAttribute `json:"attributes" required:"true" nullable:"true"`
TotalKeys int64 `json:"totalKeys" required:"true"`
Attributes []MetricAttribute `json:"attributes" required:"true" nullable:"true" description:"Attributes of the metric, ordered by distinct value count descending."`
TotalKeys int64 `json:"totalKeys" required:"true" description:"Number of attributes returned."`
}
// ListMetricsParams represents the query parameters for the list metrics endpoint.
type ListMetricsParams struct {
Start *int64 `query:"start"`
End *int64 `query:"end"`
Limit int `query:"limit"`
Search string `query:"searchText"`
Source string `query:"source"`
Start *int64 `query:"start" description:"Start of the time range as a Unix timestamp in milliseconds. Must be greater than 0; applied only when end is also set."`
End *int64 `query:"end" description:"End of the time range as a Unix timestamp in milliseconds. Must be greater than start; applied only when start is also set."`
Limit int `query:"limit" description:"Maximum number of metric names to return. Defaults to 100; must not exceed 5000."`
Search string `query:"searchText" description:"Case-insensitive substring to match against metric names."`
Source string `query:"source" description:"Set to meter to list Cost Meter metrics instead of telemetry metrics."`
}
// Validate ensures ListMetricsParams contains acceptable values.
@@ -353,30 +353,30 @@ func (p *ListMetricsParams) Validate() error {
// ListMetric represents a single metric with its metadata in the list metrics response.
type ListMetric struct {
MetricName string `json:"metricName" required:"true"`
Description string `json:"description" required:"true"`
MetricType metrictypes.Type `json:"type" required:"true"`
MetricUnit string `json:"unit" required:"true"`
Temporality metrictypes.Temporality `json:"temporality" required:"true"`
IsMonotonic bool `json:"isMonotonic" required:"true"`
MetricName string `json:"metricName" required:"true" description:"The name of the metric."`
Description string `json:"description" required:"true" description:"Human-readable description from the metric metadata; empty when none was reported."`
MetricType metrictypes.Type `json:"type" required:"true" description:"The metric type, such as gauge, sum, histogram, summary or exponentialhistogram."`
MetricUnit string `json:"unit" required:"true" description:"The unit reported in the metric metadata, for example ms or By; empty when none was reported."`
Temporality metrictypes.Temporality `json:"temporality" required:"true" description:"Aggregation temporality of the metric: cumulative, delta or unspecified."`
IsMonotonic bool `json:"isMonotonic" required:"true" description:"True when the metric is a monotonically increasing Sum (a counter)."`
}
// ListMetricsResponse represents the response for the list metrics endpoint.
type ListMetricsResponse struct {
Metrics []ListMetric `json:"metrics" required:"true" nullable:"true"`
Metrics []ListMetric `json:"metrics" required:"true" nullable:"true" description:"Matching metrics with their metadata, ordered by name."`
}
// MetricsOnboardingResponse is the response for the lightweight metrics onboarding check.
type MetricsOnboardingResponse struct {
HasMetrics bool `json:"hasMetrics" required:"true"`
HasMetrics bool `json:"hasMetrics" required:"true" description:"True when at least one metric not emitted by SigNoz itself has been ingested."`
}
// InspectMetricsRequest is the request for inspecting raw metric data points.
type InspectMetricsRequest struct {
MetricName string `json:"metricName" required:"true"`
Start int64 `json:"start" required:"true"`
End int64 `json:"end" required:"true"`
Filter *qbtypes.Filter `json:"filter,omitempty"`
MetricName string `json:"metricName" required:"true" description:"The name of the metric to inspect."`
Start int64 `json:"start" required:"true" description:"Start of the time range as a Unix timestamp in milliseconds. Must be greater than 0 and less than end."`
End int64 `json:"end" required:"true" description:"End of the time range as a Unix timestamp in milliseconds. At most 30 minutes after start."`
Filter *qbtypes.Filter `json:"filter,omitempty" description:"Query builder filter over the metric attributes, for example host.name = 'web-1'. Omitting it applies no attribute restriction; inspection returns at most 50 series either way."`
}
// Validate ensures the request is valid.
@@ -399,5 +399,5 @@ func (r *InspectMetricsRequest) Validate() error {
// InspectMetricsResponse is the response for the inspect metrics endpoint.
type InspectMetricsResponse struct {
Series []*qbtypes.TimeSeries `json:"series" required:"true" nullable:"true"`
Series []*qbtypes.TimeSeries `json:"series" required:"true" nullable:"true" description:"Raw time series, at most 50, each with its labels and timestamp-value pairs."`
}

View File

@@ -277,8 +277,8 @@ type MetricValueFilter struct {
}
type ComparisonSpaceAggregationParam struct {
Operater string `json:"operator" required:"true"`
Threshold float64 `json:"threshold" required:"true"`
Operater string `json:"operator" required:"true" description:"Comparison applied to histogram bucket bounds when spaceAggregation is count: <= gives the estimated count of observations at or below threshold, > the estimated count above it."`
Threshold float64 `json:"threshold" required:"true" description:"Bucket bound the comparison uses, in the unit of the histogram metric. The count is an estimate, interpolated linearly between the two nearest bucket bounds."`
}
func (param ComparisonSpaceAggregationParam) StringValue() string {

View File

@@ -497,10 +497,8 @@ func FunctionReduceTo(result *TimeSeries, reduceTo ReduceTo) *TimeSeries {
}
type TraceAggregation struct {
// aggregation expression - example: count(), sum(item_price), countIf(day > 10)
Expression string `json:"expression"`
// if any, it will be used as the alias of the aggregation in the result
Alias string `json:"alias,omitempty"`
Expression string `json:"expression" description:"The aggregation over spans, such as count(), sum(item_price), p99(duration_nano) or countIf(day > 10)."`
Alias string `json:"alias,omitempty" description:"The name the aggregation is reported under; when omitted, the result carries the alias __result_N, with N the zero-based index of the aggregation."`
}
// Copy creates a deep copy of TraceAggregation.
@@ -509,10 +507,8 @@ func (t TraceAggregation) Copy() TraceAggregation {
}
type LogAggregation struct {
// aggregation expression - example: count(), sum(item_price), countIf(day > 10)
Expression string `json:"expression"`
// if any, it will be used as the alias of the aggregation in the result
Alias string `json:"alias,omitempty"`
Expression string `json:"expression" description:"The aggregation over log records, such as count(), sum(item_price), count_distinct(user_id) or countIf(day > 10)."`
Alias string `json:"alias,omitempty" description:"The name the aggregation is reported under; when omitted, the result carries the alias __result_N, with N the zero-based index of the aggregation."`
}
// Copy creates a deep copy of LogAggregation.
@@ -521,24 +517,18 @@ func (l LogAggregation) Copy() LogAggregation {
}
type MetricAggregation struct {
// metric to query
MetricName string `json:"metricName"`
MetricName string `json:"metricName" description:"The name of the metric to query, for example k8s.pod.cpu_request_utilization."`
// type of the metric
Type metrictypes.Type `json:"-"`
// temporality to apply to the query
Temporality metrictypes.Temporality `json:"temporality"`
// time aggregation to apply to the query
TimeAggregation metrictypes.TimeAggregation `json:"timeAggregation"`
// space aggregation to apply to the query
SpaceAggregation metrictypes.SpaceAggregation `json:"spaceAggregation"`
// param for space aggregation if needed
ComparisonSpaceAggregationParam *metrictypes.ComparisonSpaceAggregationParam `json:"comparisonSpaceAggregationParam,omitempty"`
Type metrictypes.Type `json:"-"`
Temporality metrictypes.Temporality `json:"temporality" description:"The temporality of the metric: delta, cumulative or unspecified. Resolved from the metric metadata when omitted."`
TimeAggregation metrictypes.TimeAggregation `json:"timeAggregation" description:"How samples of one series are combined within each step, such as avg, sum, min, max, count, count_distinct, latest, rate or increase."`
SpaceAggregation metrictypes.SpaceAggregation `json:"spaceAggregation" description:"How series are combined across each group, such as sum, avg, min, max or count; histograms take percentiles such as p50, p90, p95 or p99."`
ComparisonSpaceAggregationParam *metrictypes.ComparisonSpaceAggregationParam `json:"comparisonSpaceAggregationParam,omitempty" description:"Histogram metrics with spaceAggregation count only: the comparison operator and bucket bound used to estimate how many observations fall at or below, or above, the threshold. Ignored for other metric types and aggregations."`
// table hints to use for the query
TableHints *metrictypes.MetricTableHints `json:"-"`
// value filter to apply to the query
ValueFilter *metrictypes.MetricValueFilter `json:"-"`
// reduce to operator for metric scalar requests
ReduceTo ReduceTo `json:"reduceTo,omitzero"`
ReduceTo ReduceTo `json:"reduceTo,omitzero" description:"How a series collapses to one number in scalar requests: sum, count, avg, min, max, last or median. Ignored by time_series requests."`
HeatmapBucketing *HeatmapBucketing `json:"-"`
@@ -564,8 +554,7 @@ func (m MetricAggregation) Copy() MetricAggregation {
}
type Filter struct {
// expression to filter by following the filter syntax
Expression string `json:"expression"`
Expression string `json:"expression" description:"A filter in the query filter syntax: comparisons such as =, !=, <, >, IN, LIKE, CONTAINS and EXISTS on field names, combined with AND, OR and NOT, with string values in single quotes, for example key = 'value' AND other_key > 10. Which fields exist depends on the signal or endpoint the filter is used with."`
}
// Copy creates a deep copy of Filter.
@@ -588,8 +577,7 @@ func (g GroupByKey) Copy() GroupByKey {
}
type Having struct {
// expression to filter by following the filter syntax
Expression string `json:"expression"`
Expression string `json:"expression" description:"A condition on aggregation results in the filter syntax, referencing an alias or expression, for example count() > 100."`
}
// Copy creates a deep copy of Having.
@@ -613,10 +601,8 @@ func (o OrderByKey) Copy() OrderByKey {
// key to order by.
type OrderBy struct {
// key to order by
Key OrderByKey `json:"key"`
// direction to order by
Direction OrderDirection `json:"direction"`
Key OrderByKey `json:"key" description:"The field, aggregation alias or expression to sort by, for example timestamp, service.name or count()."`
Direction OrderDirection `json:"direction" description:"The sort direction: asc or desc."`
}
// Copy creates a deep copy of OrderBy.
@@ -629,21 +615,13 @@ func (o OrderBy) Copy() OrderBy {
// secondary aggregation to apply to the query.
type SecondaryAggregation struct {
// stepInterval of the query
// if not set, it will use the step interval of the primary aggregation
StepInterval Step `json:"stepInterval,omitzero"`
// expression to aggregate. example: count(), sum(item_price), countIf(day > 10)
Expression string `json:"expression"`
// if any, it will be used as the alias of the aggregation in the result
Alias string `json:"alias,omitempty"`
// groupBy fields to group by
GroupBy []GroupByKey `json:"groupBy,omitzero"`
// order by keys and directions
Order []OrderBy `json:"order,omitzero"`
// limit the maximum number of rows to return
Limit int `json:"limit,omitempty"`
// limitBy fields to limit by
LimitBy *LimitBy `json:"limitBy,omitempty"`
StepInterval Step `json:"stepInterval,omitzero" description:"The time bucket for this aggregation: seconds as a number or a Go duration string. Uses the primary aggregation step when omitted."`
Expression string `json:"expression" description:"The aggregation over the primary results, such as count(), sum(item_price) or countIf(day > 10)."`
Alias string `json:"alias,omitempty" description:"The name the aggregation is reported under; when omitted, the result carries the alias __result_N, with N the zero-based index of the aggregation."`
GroupBy []GroupByKey `json:"groupBy,omitzero" description:"The fields to group this aggregation by."`
Order []OrderBy `json:"order,omitzero" description:"The sort order of this aggregation result."`
Limit int `json:"limit,omitempty" description:"The maximum number of groups to return. Unlimited when 0."`
LimitBy *LimitBy `json:"limitBy,omitempty" description:"Applies limit within each distinct combination of the given keys."`
}
// Copy creates a deep copy of SecondaryAggregation.
@@ -672,10 +650,8 @@ func (s SecondaryAggregation) Copy() SecondaryAggregation {
}
type FunctionArg struct {
// name of the argument
Name string `json:"name,omitempty"`
// value of the argument
Value any `json:"value"`
Name string `json:"name,omitempty" description:"The argument name for functions that take named arguments, such as z_score_threshold or seasonality for anomaly."`
Value any `json:"value" description:"The argument value as a number, string or boolean, for example the threshold for cutOffMin or the duration for timeShift."`
}
// Copy creates a deep copy of FunctionArg.
@@ -695,6 +671,7 @@ func (FunctionArg) PrepareJSONSchema(s *jsonschema.Schema) error {
}
value := jsonschema.Schema{}
value.Description = propertyDescription(s, "value")
value.OneOf = []jsonschema.SchemaOrBool{
jsonschema.Number.ToSchemaOrBool(),
jsonschema.String.ToSchemaOrBool(),
@@ -705,11 +682,9 @@ func (FunctionArg) PrepareJSONSchema(s *jsonschema.Schema) error {
}
type Function struct {
// name of the function
Name FunctionName `json:"name"`
Name FunctionName `json:"name" description:"The function to apply, such as cutOffMin, cutOffMax, clampMin, clampMax, absolute, runningDiff, log2, log10, cumulativeSum, ewma3, ewma5, ewma7, median3, median5, median7, timeShift, anomaly or fillZero."`
// args is the arguments to the function
Args []FunctionArg `json:"args,omitzero"`
Args []FunctionArg `json:"args,omitzero" description:"The function arguments; which ones are required depends on the function, for example a threshold for the cutOff and clamp functions."`
}
// Copy creates a deep copy of Function.
@@ -739,10 +714,8 @@ func (f Function) Validate() error {
}
type LimitBy struct {
// keys to limit by
Keys []string `json:"keys"`
// value to limit by
Value string `json:"value"`
Keys []string `json:"keys" description:"The group-by field names that partition the result before the limit is applied."`
Value string `json:"value" description:"The aggregation alias or expression whose value ranks entries within each partition."`
}
// Copy creates a deep copy of LimitBy.
@@ -759,3 +732,13 @@ func (l *LimitBy) Copy() *LimitBy {
}
return c
}
// propertyDescription returns the description the struct tag put on a reflected
// property, so a PrepareJSONSchema hook that replaces the property schema keeps it.
func propertyDescription(s *jsonschema.Schema, name string) *string {
prop, ok := s.Properties[name]
if !ok || prop.TypeObject == nil {
return nil
}
return prop.TypeObject.Description
}

View File

@@ -12,65 +12,43 @@ import (
)
type QueryBuilderQuery[T any] struct {
// name of the query, mainly used when query is used in formula
Name string `json:"name"`
Name string `json:"name" description:"The unique name of the query, such as A or B; formulas, trace operators and selectedQueryName refer to it."`
// stepInterval of the query
StepInterval Step `json:"stepInterval,omitzero"`
StepInterval Step `json:"stepInterval,omitzero" description:"The time bucket for time_series aggregation: seconds as a number or a Go duration string such as 60s or 1m. Derived from the time range when omitted."`
// signal to query
Signal telemetrytypes.Signal `json:"signal,omitempty"`
Signal telemetrytypes.Signal `json:"signal,omitempty" description:"The telemetry signal to query: traces, logs or metrics. It selects the aggregation shape and is required for builder_query."`
// source for query
Source telemetrytypes.Source `json:"source"`
Source telemetrytypes.Source `json:"source" description:"The data source within the signal. Set to meter for Cost Meter metrics; leave empty for regular telemetry."`
// we want to support multiple aggregations
// currently supported: []Aggregation, []MetricAggregation
Aggregations []T `json:"aggregations,omitzero"`
Aggregations []T `json:"aggregations,omitzero" description:"The aggregations to compute, one result per entry. Traces and logs take expressions such as count() or p99(duration_nano); metrics take a metricName with time and space aggregations. Omit for raw requests."`
// BucketOptions is the bucket axis to count this query's values into. Only a
// heatmap request reads it, and only from the query it draws.
BucketOptions *BucketOptions `json:"bucketOptions,omitempty"`
BucketOptions *BucketOptions `json:"bucketOptions,omitempty" description:"The value axis to count this query into, as a kind-discriminated linear or log bucket spec. Read only by heatmap requests, and only from the query they draw."`
// disabled if true, the query will not be executed
Disabled bool `json:"disabled"`
Disabled bool `json:"disabled" description:"When true, the result is excluded from the response; the query may still run and can still feed a formula or trace operator. Defaults to false."`
// search query is simple string
Filter *Filter `json:"filter,omitempty"`
Filter *Filter `json:"filter,omitempty" description:"The filter expression that selects the rows or series of this signal."`
// group by keys to group by
GroupBy []GroupByKey `json:"groupBy,omitzero"`
GroupBy []GroupByKey `json:"groupBy,omitzero" description:"The fields to group aggregations by; each group becomes a series or a row with the field as a label or column."`
// order by keys and directions
Order []OrderBy `json:"order,omitzero"`
Order []OrderBy `json:"order,omitzero" description:"The sort order of the result. Order by a group-by field, an aggregation alias, or for raw requests a selected field such as timestamp."`
// select columns to select
SelectFields []telemetrytypes.TelemetryFieldKey `json:"selectFields,omitzero"`
SelectFields []telemetrytypes.TelemetryFieldKey `json:"selectFields,omitzero" description:"The fields to return for each row in raw requests, in addition to the columns always present: timestamp and id for logs, timestamp, trace_id and span_id for traces. Ignored by aggregating requests."`
// limit the maximum number of rows to return
Limit int `json:"limit,omitempty"`
Limit int `json:"limit,omitempty" description:"The maximum number of rows for raw requests, or of series or rows for grouped aggregations, keeping the top entries by order. When 0, aggregations are unlimited while raw requests ordered by timestamp return a page of 10000 rows; follow nextCursor for the rest."`
// limitBy fields to limit by
LimitBy *LimitBy `json:"limitBy,omitempty"`
LimitBy *LimitBy `json:"limitBy,omitempty" description:"Reserved for applying limit within each combination of the given group-by keys. Accepted but currently ignored."`
// offset the number of rows to skip
// TODO: remove this once we have cursor-based pagination everywhere?
Offset int `json:"offset,omitempty"`
Offset int `json:"offset,omitempty" description:"The number of rows to skip before returning results. Prefer cursor for raw requests."`
// cursor to paginate the query
Cursor string `json:"cursor,omitempty"`
Cursor string `json:"cursor,omitempty" description:"The opaque nextCursor value from a previous raw result, used to fetch the next page."`
// having clause to apply to the query
Having *Having `json:"having,omitempty"`
Having *Having `json:"having,omitempty" description:"A filter applied to aggregation results, referencing aliases or expressions, for example count() > 100."`
// secondary aggregation to apply to the query
// on top of the primary aggregation
SecondaryAggregations []SecondaryAggregation `json:"secondaryAggregations,omitzero"`
SecondaryAggregations []SecondaryAggregation `json:"secondaryAggregations,omitzero" description:"Accepted but currently ignored, including their nested order, limit and limitBy."`
// functions to apply to the query
Functions []Function `json:"functions,omitzero"`
Functions []Function `json:"functions,omitzero" description:"Post-processing functions applied in order to each result series, such as absolute, cumulativeSum, timeShift or anomaly."`
Legend string `json:"legend"`
Legend string `json:"legend" description:"The display name template for each series; {{field}} placeholders are replaced by group-by label values."`
// ShiftBy is extracted from timeShift function for internal use
// This field is not serialized to JSON
@@ -96,7 +74,9 @@ func (QueryBuilderQuery[T]) PrepareJSONSchema(s *jsonschema.Schema) error {
return nil
}
prop := (&jsonschema.Schema{}).WithType(jsonschema.String.Type()).WithEnum(signal.StringValue())
prop.Description = propertyDescription(s, "signal")
s.Properties["signal"] = prop.ToSchemaOrBool()
describePropertyForSignal(s, "filter", filterDescriptionsBySignal[signal])
if !slices.Contains(s.Required, "signal") {
s.Required = append(s.Required, "signal")
}
@@ -292,3 +272,23 @@ func CanShortCircuitReduced(metricAgg MetricAggregation) bool {
return false
}
// filterDescriptionsBySignal gives each QueryBuilderQuery instantiation a filter
// example drawn from its own signal, so the traces example never shows up on a
// logs or metrics query. The shared Filter schema stays signal-neutral.
var filterDescriptionsBySignal = map[telemetrytypes.Signal]string{
telemetrytypes.SignalTraces: "The filter that selects spans, in the query filter syntax, for example service.name = 'frontend' AND http.response.status_code >= 500.",
telemetrytypes.SignalLogs: "The filter that selects log records, in the query filter syntax, for example severity_text = 'ERROR' AND body CONTAINS 'timeout'.",
telemetrytypes.SignalMetrics: "The filter that selects series by their attributes, in the query filter syntax, for example k8s.namespace.name = 'prod' AND k8s.pod.name LIKE 'api%'.",
}
// describePropertyForSignal replaces a property's description with a
// signal-specific one, keeping whatever schema shape the property already has.
func describePropertyForSignal(s *jsonschema.Schema, name, description string) {
prop, ok := s.Properties[name]
if !ok || prop.TypeObject == nil || description == "" {
return
}
prop.TypeObject.Description = &description
s.Properties[name] = prop
}

View File

@@ -1,14 +1,11 @@
package querybuildertypesv5
type ClickHouseQuery struct {
// name of the query
Name string `json:"name"`
// query to execute
Query string `json:"query"`
// disabled if true, the query will not be executed
Disabled bool `json:"disabled"`
Name string `json:"name" description:"The unique name of the query, such as A."`
Query string `json:"query" description:"The ClickHouse SQL statement. Reserved time placeholders such as {{.start_timestamp}}, {{.end_timestamp}}, {{.start_timestamp_ms}} and {{.start_datetime}} and dashboard variables written as {{name}}, [[name]] or $name are substituted before execution."`
Disabled bool `json:"disabled" description:"When true, the result is excluded from the response but the query may still run. Defaults to false."`
Legend string `json:"legend"`
Legend string `json:"legend" description:"The display name template for each series; {{column}} placeholders are replaced by result column values."`
}
// Copy creates a deep copy of the ClickHouseQuery.

View File

@@ -17,30 +17,22 @@ import (
)
type QueryBuilderFormula struct {
// name of the formula
Name string `json:"name"`
// expression to apply to the query
Expression string `json:"expression"`
Name string `json:"name" description:"The unique name of the formula, such as F1, used by selectedQueryName and by panels. Referencing a formula from another formula is not reliable."`
Expression string `json:"expression" description:"An arithmetic expression over builder query names, for example A / B * 100. Reference queries, not other formulas."`
Disabled bool `json:"disabled"`
Disabled bool `json:"disabled" description:"When true, the result is excluded from the response; the formula may still be evaluated. Defaults to false."`
// order by keys and directions
Order []OrderBy `json:"order,omitzero"`
Order []OrderBy `json:"order,omitzero" description:"The sort order of the formula result."`
// limit the maximum number of rows to return
Limit int `json:"limit,omitempty"`
Limit int `json:"limit,omitempty" description:"The maximum number of series or rows to return. Unlimited when 0."`
// having clause to apply to the formula result
Having *Having `json:"having,omitempty"`
Having *Having `json:"having,omitempty" description:"Accepted but currently ignored; the computed values are returned unfiltered."`
// functions to apply to the formula result
Functions []Function `json:"functions,omitzero"`
Functions []Function `json:"functions,omitzero" description:"Post-processing functions applied in order to each result series."`
// BucketOptions is the bucket axis to count the formula's results into.
// Only a heatmap request reads it, and only from the query it draws.
BucketOptions *BucketOptions `json:"bucketOptions,omitempty"`
BucketOptions *BucketOptions `json:"bucketOptions,omitempty" description:"The value axis to count the formula result into, as a kind-discriminated linear or log bucket spec. Read only by heatmap requests, and only from the query they draw."`
Legend string `json:"legend"`
Legend string `json:"legend" description:"The display name template for each series; {{field}} placeholders are replaced by label values."`
}
// Copy creates a deep copy of the QueryBuilderFormula.

View File

@@ -1,18 +1,13 @@
package querybuildertypesv5
type PromQuery struct {
// name of the query
Name string `json:"name"`
// query to execute
Query string `json:"query"`
// disabled if true, the query will not be executed
Disabled bool `json:"disabled"`
// step size for the query
Step Step `json:"step"`
// stats if true, the query will return stats
Stats bool `json:"stats"`
Name string `json:"name" description:"The unique name of the query, such as A."`
Query string `json:"query" description:"The PromQL expression. Dotted metric and label names must use the UTF-8 quoted selector form, for example {'http.server.duration'}."`
Disabled bool `json:"disabled" description:"When true, the result is excluded from the response but the query may still run. Defaults to false."`
Step Step `json:"step" description:"The evaluation step: seconds as a number or a Go duration string such as 60s. Derived from the time range when omitted."`
Stats bool `json:"stats" description:"Reserved; currently has no effect."`
Legend string `json:"legend"`
Legend string `json:"legend" description:"The display name template for each series; {{label}} placeholders are replaced by label values."`
}
// Copy creates a deep copy of the PromQuery.

View File

@@ -25,17 +25,17 @@ type Result struct {
}
type ExecStats struct {
RowsScanned uint64 `json:"rowsScanned"`
BytesScanned uint64 `json:"bytesScanned"`
DurationMS uint64 `json:"durationMs"`
StepIntervals map[string]uint64 `json:"stepIntervals,omitempty"`
RowsScanned uint64 `json:"rowsScanned" description:"Rows scanned to answer every query in the request. A cost indicator only: rows are scanned whether or not any matched, so it says nothing about whether data exists."`
BytesScanned uint64 `json:"bytesScanned" description:"Bytes scanned to answer every query in the request. A cost indicator only; it does not indicate whether any data matched."`
DurationMS uint64 `json:"durationMs" description:"Total execution time across the queries in the request, in milliseconds. A cost indicator only, not the request latency and not a sign that data exists."`
StepIntervals map[string]uint64 `json:"stepIntervals,omitempty" description:"The step interval in seconds that was used for each query, keyed by query name."`
}
var _ jsonschema.Preparer = &ExecStats{}
// PrepareJSONSchema adds description to the ExecStats schema.
func (e *ExecStats) PrepareJSONSchema(schema *jsonschema.Schema) error {
schema.WithDescription("Execution statistics for the query, including rows scanned, bytes scanned, and duration.")
schema.WithDescription("Execution cost of the request: rows and bytes scanned and execution time. A measure of cost, not of whether data exists.")
return nil
}

View File

@@ -16,10 +16,8 @@ import (
)
type QueryEnvelope struct {
// Type is the type of the query.
Type QueryType `json:"type"` // "builder_query" | "builder_formula" | "builder_sub_query" | "builder_join" | "promql" | "clickhouse_sql"
// Spec is the deferred decoding of the query if any.
Spec any `json:"spec"`
Type QueryType `json:"type" description:"The kind of query the spec holds: builder_query, builder_ai_query, builder_formula, builder_trace_operator, promql or clickhouse_sql."`
Spec any `json:"spec" description:"The query specification; its shape is selected by type, and by signal for builder_query."`
}
// builderQuerySpec is a signal-discriminated oneOf of the three
@@ -262,8 +260,7 @@ func UnmarshalBuilderQueryBySignal(data []byte) (any, error) {
}
type CompositeQuery struct {
// Queries is the queries to use for the request.
Queries []QueryEnvelope `json:"queries"`
Queries []QueryEnvelope `json:"queries" description:"The queries to run, each wrapped in a type-discriminated envelope. Names must be unique; formulas and trace operators reference other queries by name."`
}
// PrepareJSONSchema adds description to the CompositeQuery schema.
@@ -333,8 +330,8 @@ func (VariableType) Enum() []any {
}
type VariableItem struct {
Type VariableType `json:"type"`
Value any `json:"value"`
Type VariableType `json:"type" description:"The kind of dashboard variable: query, dynamic, custom or text."`
Value any `json:"value" description:"The selected value: a string, number or boolean, or a list of them for multi-select variables. The string __all__ is special only for dynamic variables in builder filters and PromQL, where it removes the condition that references the variable; query, custom and text variables, and ClickHouse SQL for every type, substitute it literally."`
}
var _ jsonschema.Preparer = VariableItem{}
@@ -360,6 +357,7 @@ func (VariableItem) PrepareJSONSchema(s *jsonschema.Schema) error {
list.WithItems(items)
value := jsonschema.Schema{}
value.Description = propertyDescription(s, "value")
value.OneOf = []jsonschema.SchemaOrBool{
jsonschema.String.ToSchemaOrBool(),
jsonschema.Number.ToSchemaOrBool(),
@@ -372,28 +370,21 @@ func (VariableItem) PrepareJSONSchema(s *jsonschema.Schema) error {
}
type QueryRangeRequest struct {
// SchemaVersion is the version of the schema to use for the request payload.
SchemaVersion string `json:"schemaVersion"`
// Start is the start time of the query in epoch milliseconds.
Start uint64 `json:"start"`
// End is the end time of the query in epoch milliseconds.
End uint64 `json:"end"`
// RequestType is the type of the request.
RequestType RequestType `json:"requestType"`
// CompositeQuery is the composite query to use for the request.
CompositeQuery CompositeQuery `json:"compositeQuery"`
// Variables is the variables to use for the request.
Variables map[string]VariableItem `json:"variables,omitempty"`
SchemaVersion string `json:"schemaVersion" description:"The version of the request payload schema, currently v1."`
Start uint64 `json:"start" description:"The inclusive start of the time range as a Unix timestamp in milliseconds."`
End uint64 `json:"end" description:"The end of the time range as a Unix timestamp in milliseconds; must be greater than start except for raw_stream requests."`
RequestType RequestType `json:"requestType" description:"The shape of the result: scalar, time_series, raw, raw_stream, trace, distribution or heatmap. It decides which result type data.results carries."`
CompositeQuery CompositeQuery `json:"compositeQuery" description:"The queries to execute together; at least one is required and formulas may reference the others by name."`
Variables map[string]VariableItem `json:"variables,omitempty" description:"Dashboard variable values keyed by variable name. Builder filters substitute only the $name form and leave a quoted {{name}} unchanged; PromQL and ClickHouse SQL substitute $name, {{name}}, [[name]] and the Go template form {{.name}}."`
// NoCache is a flag to disable caching for the request.
NoCache bool `json:"noCache,omitempty"`
NoCache bool `json:"noCache,omitempty" description:"When true, results are computed fresh instead of being served from the query result cache. Defaults to false."`
FormatOptions *FormatOptions `json:"formatOptions,omitempty"`
FormatOptions *FormatOptions `json:"formatOptions,omitempty" description:"Post-processing applied to the result before it is returned."`
}
// PrepareJSONSchema adds description to the QueryRangeRequest schema.
func (q *QueryRangeRequest) PrepareJSONSchema(schema *jsonschema.Schema) error {
schema.WithDescription("Request body for the v5 query range endpoint. Supports builder queries (traces, logs, metrics), formulas, joins, trace operators, PromQL, and ClickHouse SQL queries.")
schema.WithDescription("Request body for the v5 query range endpoint. Supports builder queries (traces, logs, metrics), formulas, trace operators, PromQL, and ClickHouse SQL queries.")
return nil
}
@@ -677,8 +668,8 @@ func (r *QueryRangeRequest) UnmarshalJSON(data []byte) error {
}
type FormatOptions struct {
FillGaps bool `json:"fillGaps,omitempty"`
FormatTableResultForUI bool `json:"formatTableResultForUI,omitempty"`
FillGaps bool `json:"fillGaps,omitempty" description:"When true, every builder query and formula in a time_series result gets a zero value for each missing step, whatever its aggregation, so averages and percentiles read as zero where no data exists. PromQL and ClickHouse SQL queries keep their gaps. Defaults to false."`
FormatTableResultForUI bool `json:"formatTableResultForUI,omitempty" description:"When true, scalar results from several queries are merged into one table keyed by their group-by columns. Defaults to false."`
}
func (r *QueryRangeRequest) GetQueriesSupportingZeroDefault() map[string]bool {
@@ -753,8 +744,8 @@ func (BucketsKind) Enum() []any {
type LinearBucketsSpec struct {
// Everything above MaxValue is counted in the trailing overflow band. Evenly
// spaced upper bounds have no top to divide without it, so it is required.
MaxValue float64 `json:"maxValue" required:"true"`
NumBuckets int `json:"numBuckets,omitempty"`
MaxValue float64 `json:"maxValue" required:"true" description:"The upper bound of the last regular band; values above it are counted in a trailing overflow band."`
NumBuckets int `json:"numBuckets,omitempty" description:"The number of evenly spaced bands between 0 and maxValue. A default applies when omitted or 0."`
}
// LogBucketsSpec spaces upper bounds at 2^Scale bands per doubling, the mapping
@@ -763,7 +754,7 @@ type LogBucketsSpec struct {
// ClickHouse always buckets at MaxLogScale and the surplus is folded away
// afterwards, so every Scale reads the same cache entry. MaxLogScale applies
// when unset.
Scale *int `json:"scale,omitempty"`
Scale *int `json:"scale,omitempty" description:"The exponential histogram scale: 2^scale bands per doubling of the value. The maximum supported scale applies when omitted."`
}
func (b *BucketOptions) UnmarshalJSON(data []byte) error {

View File

@@ -32,17 +32,17 @@ type QBEvent struct {
}
type QueryWarnData struct {
Message string `json:"message"`
Url string `json:"url,omitempty"`
Warnings []QueryWarnDataAdditional `json:"warnings,omitempty"`
Message string `json:"message" description:"A summary of the warnings raised while executing the request."`
Url string `json:"url,omitempty" description:"A documentation link explaining the warnings."`
Warnings []QueryWarnDataAdditional `json:"warnings,omitempty" description:"The individual warnings."`
}
type QueryWarnDataAdditional struct {
Message string `json:"message"`
Message string `json:"message" description:"The warning text."`
}
type QueryData struct {
Results []any `json:"results"`
Results []any `json:"results" description:"One result per enabled query, typed by the request type: TimeSeriesData for time_series, ScalarData for scalar and RawData for raw requests."`
}
var _ jsonschema.OneOfExposer = QueryData{}
@@ -57,11 +57,11 @@ func (QueryData) JSONSchemaOneOf() []any {
}
type QueryRangeResponse struct {
Type RequestType `json:"type"`
Data QueryData `json:"data"`
Meta ExecStats `json:"meta"`
Type RequestType `json:"type" description:"The request type the results are shaped for, echoed from the request."`
Data QueryData `json:"data" description:"The query results."`
Meta ExecStats `json:"meta" description:"Execution statistics for the whole request: a rough measure of how expensive the query was. Use the results, not these numbers, to decide whether data exists; an empty result can still scan many rows."`
Warning *QueryWarnData `json:"warning,omitempty"`
Warning *QueryWarnData `json:"warning,omitempty" description:"Warnings raised while executing the request, if any."`
QBEvent *QBEvent `json:"-"`
}
@@ -69,7 +69,7 @@ type QueryRangeResponse struct {
// QueryRangePreviewResponse is the dry-run output: one QueryPreview per query,
// keyed by the request's query names.
type QueryRangePreviewResponse struct {
CompositeQuery map[string]QueryPreview `json:"compositeQuery" required:"true" nullable:"true"`
CompositeQuery map[string]QueryPreview `json:"compositeQuery" required:"true" nullable:"true" description:"The rendered statements for each query, keyed by query name."`
}
// QueryRangePreviewOptions carries per-call options for the dry-run endpoint.
@@ -90,20 +90,20 @@ func (q *QueryRangePreviewResponse) PrepareJSONSchema(schema *jsonschema.Schema)
// QueryPreview is the dry-run result for a single query.
type QueryPreview struct {
Valid bool `json:"valid" required:"true" nullable:"false"`
Error error `json:"error" required:"true"`
Warnings []string `json:"warnings" required:"true" nullable:"false"`
Statements []PreviewStatement `json:"statements" required:"true" nullable:"false"`
Valid bool `json:"valid" required:"true" nullable:"false" description:"Whether the query rendered without error."`
Error error `json:"error" required:"true" description:"The rendering error when valid is false, otherwise null."`
Warnings []string `json:"warnings" required:"true" nullable:"false" description:"Warnings raised while rendering the query."`
Statements []PreviewStatement `json:"statements" required:"true" nullable:"false" description:"The rendered statements, returned only when verbose is true: one per PromQL metric selector, one for builder, ClickHouse SQL and trace operator queries, none for formulas. Empty otherwise."`
}
// PreviewStatement is one rendered ClickHouse statement with its args and, when
// requested, its EXPLAIN ESTIMATE and granule breakdown. The query/args JSON
// keys follow the OpenTelemetry db.statement.* convention.
type PreviewStatement struct {
Query string `json:"db.statement.query" required:"true" nullable:"false"`
Args []any `json:"db.statement.args" required:"true" nullable:"false"`
Estimate []telemetrystoretypes.EstimateEntry `json:"estimate" required:"true" nullable:"false"`
Granules *telemetrystoretypes.Granules `json:"granules" required:"true" nullable:"true"`
Query string `json:"db.statement.query" required:"true" nullable:"false" description:"The ClickHouse SQL statement with positional parameter placeholders."`
Args []any `json:"db.statement.args" required:"true" nullable:"false" description:"The values bound to the statement placeholders, in order."`
Estimate []telemetrystoretypes.EstimateEntry `json:"estimate" required:"true" nullable:"false" description:"The EXPLAIN ESTIMATE rows for the statement; empty unless verbose was requested."`
Granules *telemetrystoretypes.Granules `json:"granules" required:"true" nullable:"true" description:"The granule analysis for the statement; null unless verbose was requested."`
}
// MarshalJSON renders Error in its structured form (code/message/suggestions)
@@ -133,20 +133,20 @@ func (q *QueryRangeResponse) PrepareJSONSchema(schema *jsonschema.Schema) error
}
type TimeSeriesData struct {
QueryName string `json:"queryName"`
Aggregations []*AggregationBucket `json:"aggregations"`
QueryName string `json:"queryName" description:"The name of the query that produced this result."`
Aggregations []*AggregationBucket `json:"aggregations" description:"One bucket per aggregation of the query. The order is not guaranteed; match buckets to aggregations by index."`
}
type AggregationBucket struct {
Index int `json:"index"` // or string Alias
Alias string `json:"alias"`
Meta AggregationMeta `json:"meta,omitempty"`
Series []*TimeSeries `json:"series"` // no extra nesting
Index int `json:"index" description:"The zero-based position of the aggregation in the query."`
Alias string `json:"alias" description:"The alias of the aggregation, or the internal name __result_N, with N its index, when no alias was given."`
Meta AggregationMeta `json:"meta,omitempty" description:"Display metadata for the aggregation."`
Series []*TimeSeries `json:"series" description:"One series per group-by combination."`
PredictedSeries []*TimeSeries `json:"predictedSeries,omitempty"`
UpperBoundSeries []*TimeSeries `json:"upperBoundSeries,omitempty"`
LowerBoundSeries []*TimeSeries `json:"lowerBoundSeries,omitempty"`
AnomalyScores []*TimeSeries `json:"anomalyScores,omitempty"`
PredictedSeries []*TimeSeries `json:"predictedSeries,omitempty" description:"The predicted values per series; present only when the anomaly function is applied."`
UpperBoundSeries []*TimeSeries `json:"upperBoundSeries,omitempty" description:"The upper bound of the expected range per series; present only when the anomaly function is applied."`
LowerBoundSeries []*TimeSeries `json:"lowerBoundSeries,omitempty" description:"The lower bound of the expected range per series; present only when the anomaly function is applied."`
AnomalyScores []*TimeSeries `json:"anomalyScores,omitempty" description:"The anomaly score per series; present only when the anomaly function is applied."`
}
// ReindexValuesToNewUpperBounds moves each count to the index its upper bound
@@ -190,16 +190,13 @@ func (a *AggregationBucket) ReindexValuesToNewUpperBounds(onto []float64) {
}
type AggregationMeta struct {
Unit string `json:"unit,omitempty"`
// Buckets holds ascending upper bounds shared by every series in the
// AggregationBucket, set only for heatmap results. Each point's Values holds
// len(Buckets)+1 counts: one per bound, then the open-above overflow.
Buckets []float64 `json:"buckets,omitempty"`
Unit string `json:"unit,omitempty" description:"The unit of the aggregated values, taken from metric metadata when known."`
Buckets []float64 `json:"buckets,omitempty" description:"The ascending upper bounds of the value bands, set only for heatmap results; each point then carries one count per bound plus a trailing overflow count in values."`
}
type TimeSeries struct {
Labels []*Label `json:"labels,omitempty"`
Values []*TimeSeriesValue `json:"values"`
Labels []*Label `json:"labels,omitempty" description:"The group-by field values that identify this series."`
Values []*TimeSeriesValue `json:"values" description:"The data points of the series in ascending timestamp order."`
}
// EvaluableValues returns only the values where Partial is false and value is not NaN or +/- Inf.
@@ -218,8 +215,8 @@ func (ts *TimeSeries) EvaluableValues() []*TimeSeriesValue {
}
type Label struct {
Key telemetrytypes.TelemetryFieldKey `json:"key"`
Value any `json:"value"`
Key telemetrytypes.TelemetryFieldKey `json:"key" description:"The group-by field this label value belongs to."`
Value any `json:"value" description:"The label value as a string, number or boolean."`
}
var _ jsonschema.Preparer = Label{}
@@ -232,6 +229,7 @@ func (Label) PrepareJSONSchema(s *jsonschema.Schema) error {
}
value := jsonschema.Schema{}
value.Description = propertyDescription(s, "value")
value.OneOf = []jsonschema.SchemaOrBool{
jsonschema.String.ToSchemaOrBool(),
jsonschema.Number.ToSchemaOrBool(),
@@ -290,19 +288,12 @@ func GetUniqueSeriesKey(labels []*Label) string {
}
type TimeSeriesValue struct {
Timestamp int64 `json:"timestamp"`
Value float64 `json:"value"`
Timestamp int64 `json:"timestamp" description:"The start of the step as a Unix timestamp in milliseconds."`
Value float64 `json:"value" description:"The aggregated value for the step; omitted for heatmap results, which use values."`
// true if the value is "partial", i.e doesn't cover the complete interval.
// for instance, if the query start time is 3:14:15 PM, and the step is 1 minute,
// the ts is rounded to 3:14 but the value only covers 3:14:15 PM to 3:15:00 PM
// this partial result cannot be cached and should be ignored.
// on the client side, these partial values are rendered differently.
Partial bool `json:"partial,omitempty"`
Partial bool `json:"partial,omitempty" description:"True when the step is only partly covered by the requested time range, so the value is incomplete."`
// Values holds one count per histogram bucket for heatmap results, in the
// order of the aggregation's Meta.Buckets. Value is omitted in that case.
Values []float64 `json:"values,omitempty"`
Values []float64 `json:"values,omitempty" description:"The per-band counts for heatmap results, in the order of meta.buckets plus a trailing overflow count."`
}
type ColumnType struct {
@@ -326,29 +317,29 @@ func (ColumnType) Enum() []any {
type ColumnDescriptor struct {
telemetrytypes.TelemetryFieldKey
QueryName string `json:"queryName"`
AggregationIndex int64 `json:"aggregationIndex"`
QueryName string `json:"queryName" description:"The name of the query that produced the column."`
AggregationIndex int64 `json:"aggregationIndex" description:"The zero-based position of the aggregation in the query for aggregation columns."`
Meta struct {
Unit string `json:"unit,omitempty"`
} `json:"meta,omitempty"`
Type ColumnType `json:"columnType"`
Unit string `json:"unit,omitempty" description:"The unit of the column values when known."`
} `json:"meta,omitempty" description:"Display metadata for the column."`
Type ColumnType `json:"columnType" description:"Whether the column is a group-by field (group) or an aggregated value (aggregation)."`
}
type ScalarData struct {
QueryName string `json:"queryName"`
Columns []*ColumnDescriptor `json:"columns"`
Data [][]any `json:"data"`
QueryName string `json:"queryName" description:"The name of the query that produced this table."`
Columns []*ColumnDescriptor `json:"columns" description:"The column definitions, in the same order as the values in each data row."`
Data [][]any `json:"data" description:"The rows, each a list of values aligned with columns."`
}
type RawData struct {
QueryName string `json:"queryName"`
NextCursor string `json:"nextCursor"`
Rows []*RawRow `json:"rows"`
QueryName string `json:"queryName" description:"The name of the query that produced these rows."`
NextCursor string `json:"nextCursor" description:"The cursor to pass in the next request to continue from the last row; empty when there are no more rows."`
Rows []*RawRow `json:"rows" description:"The matching records in the requested order."`
}
type RawRow struct {
Timestamp time.Time `json:"timestamp"`
Data map[string]any `json:"data"`
Timestamp time.Time `json:"timestamp" description:"The record timestamp in RFC 3339 format."`
Data map[string]any `json:"data" description:"The selected field values keyed by field name."`
}
type RawStream struct {

View File

@@ -37,35 +37,31 @@ const (
)
type QueryBuilderTraceOperator struct {
Name string `json:"name"`
Disabled bool `json:"disabled"`
Name string `json:"name" description:"The unique name of the trace operator query."`
Disabled bool `json:"disabled" description:"When true, the result is excluded from the response but the query may still run. Defaults to false."`
Expression string `json:"expression"`
Expression string `json:"expression" description:"A trace-structure expression over traces builder query names using => for direct child, -> for descendant, && for both present, || for either present and NOT for absence, for example A => B && NOT C. At most 10 operators."`
Filter *Filter `json:"filter,omitempty"`
Filter *Filter `json:"filter,omitempty" description:"Accepted but currently not applied to the matched traces. Put filters on the operand queries instead."`
// User-configurable span return strategy - which query's spans to return
ReturnSpansFrom string `json:"returnSpansFrom,omitempty"`
ReturnSpansFrom string `json:"returnSpansFrom,omitempty" description:"The name of the operand query whose spans are returned; when empty, the spans matched by the whole expression are returned."`
// Trace-specific ordering (only span_count and trace_duration allowed)
Order []OrderBy `json:"order,omitzero"`
Order []OrderBy `json:"order,omitzero" description:"The sort order of the matched traces; only span_count and trace_duration may be used as keys."`
Aggregations []TraceAggregation `json:"aggregations,omitzero"`
StepInterval Step `json:"stepInterval,omitzero"`
GroupBy []GroupByKey `json:"groupBy,omitzero"`
Aggregations []TraceAggregation `json:"aggregations,omitzero" description:"The aggregations to compute over the matched traces for scalar and time_series requests."`
StepInterval Step `json:"stepInterval,omitzero" description:"The time bucket for time_series aggregation: seconds as a number or a Go duration string such as 60s."`
GroupBy []GroupByKey `json:"groupBy,omitzero" description:"The fields to group aggregations by."`
// having clause to apply to the aggregated query results
Having *Having `json:"having,omitempty"`
Having *Having `json:"having,omitempty" description:"A condition on the aggregation results, for example count() > 10."`
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
Cursor string `json:"cursor,omitempty"`
Limit int `json:"limit,omitempty" description:"The maximum number of traces or groups to return. For raw requests 0 means 100 rows; for aggregations 0 means unlimited."`
Offset int `json:"offset,omitempty" description:"The number of raw results to skip before returning. Aggregation requests ignore it."`
Cursor string `json:"cursor,omitempty" description:"Accepted but currently ignored by the trace operator; page raw results with offset."`
Legend string `json:"legend"`
Legend string `json:"legend" description:"The display name template for each series; {{field}} placeholders are replaced by group-by label values."`
// Other post-processing options
SelectFields []telemetrytypes.TelemetryFieldKey `json:"selectFields,omitzero"`
Functions []Function `json:"functions,omitzero"`
SelectFields []telemetrytypes.TelemetryFieldKey `json:"selectFields,omitzero" description:"The span fields to return for each row in raw requests, in addition to the always-present timestamp, trace_id and span_id columns."`
Functions []Function `json:"functions,omitzero" description:"Post-processing functions applied in order to each result series."`
// Internal parsed representation (not exposed in JSON)
ParsedExpression *TraceOperand `json:"-"`

View File

@@ -100,29 +100,27 @@ func (QueryType) Enum() []any {
}
type AlertCompositeQuery struct {
Queries []qbtypes.QueryEnvelope `json:"queries" required:"true"`
Queries []qbtypes.QueryEnvelope `json:"queries" required:"true" description:"The queries to evaluate, at least one. Every envelope type must match queryType: builder_query and builder_formula for builder, promql for promql, clickhouse_sql for clickhouse_sql."`
PanelType PanelType `json:"panelType" required:"true"`
QueryType QueryType `json:"queryType" required:"true"`
// Unit for the time series data shown in the graph
// This is used to format the value and threshold
Unit string `json:"unit,omitempty"`
PanelType PanelType `json:"panelType" required:"true" description:"The panel the UI renders the queries with: graph, table or value. Use graph for alerts."`
QueryType QueryType `json:"queryType" required:"true" description:"The query language of every query: builder, promql or clickhouse_sql."`
Unit string `json:"unit,omitempty" description:"The unit of the query result, such as percent, ms or bytes. Used to format values in notifications and to convert thresholds given in a different targetUnit."`
}
type RuleCondition struct {
CompositeQuery *AlertCompositeQuery `json:"compositeQuery" required:"true"`
CompareOperator CompareOperator `json:"op,omitzero"`
Target *float64 `json:"target,omitempty" format:"double"`
AlertOnAbsent bool `json:"alertOnAbsent,omitempty"`
AbsentFor uint64 `json:"absentFor,omitempty"`
MatchType MatchType `json:"matchType,omitzero"`
TargetUnit string `json:"targetUnit,omitempty"`
Algorithm string `json:"algorithm,omitempty"`
Seasonality Seasonality `json:"seasonality,omitzero"`
SelectedQuery string `json:"selectedQueryName,omitempty"`
RequireMinPoints bool `json:"requireMinPoints,omitempty"`
RequiredNumPoints int `json:"requiredNumPoints,omitempty"`
Thresholds *RuleThresholdData `json:"thresholds,omitempty"`
CompositeQuery *AlertCompositeQuery `json:"compositeQuery" required:"true" description:"The queries whose result is compared against the thresholds."`
CompareOperator CompareOperator `json:"op,omitzero" description:"schemaVersion v1 only: how the value is compared with target. Accepted as above, below, equal, not_equal, above_or_equal, below_or_equal or outside_bounds, their symbols, or the legacy codes as strings, '1' to '7'. Required for v1; v2alpha1 sets op on each threshold."`
Target *float64 `json:"target,omitempty" format:"double" description:"schemaVersion v1 only: the value compared against, in targetUnit. Required for v1; v2alpha1 sets target on each threshold."`
AlertOnAbsent bool `json:"alertOnAbsent,omitempty" description:"Whether to fire a nodata alert when the query returns no data. Defaults to false."`
AbsentFor uint64 `json:"absentFor,omitempty" description:"How many minutes without data pass before the nodata alert fires. Ignored unless alertOnAbsent is true."`
MatchType MatchType `json:"matchType,omitzero" description:"schemaVersion v1 only: which points in the window must satisfy op. Accepted as at_least_once, all_the_times, on_average, in_total or last, their aliases avg and sum, or the legacy codes as strings, '1' to '5'. Required for v1; v2alpha1 sets matchType on each threshold."`
TargetUnit string `json:"targetUnit,omitempty" description:"schemaVersion v1 only: the unit of target, such as percent or ms. Converted to compositeQuery.unit before comparison when the two differ."`
Algorithm string `json:"algorithm,omitempty" description:"anomaly_rule only: the anomaly detection algorithm. The supported value is standard."`
Seasonality Seasonality `json:"seasonality,omitzero" description:"anomaly_rule only: the seasonal period the history is compared against, hourly, daily or weekly."`
SelectedQuery string `json:"selectedQueryName,omitempty" description:"The name of the query whose result is evaluated, such as A or F1. Must match a query in compositeQuery. When omitted, F1 is used if present, otherwise the enabled query with the highest name."`
RequireMinPoints bool `json:"requireMinPoints,omitempty" description:"Whether a series is skipped until it has at least requiredNumPoints points in the window. Defaults to false."`
RequiredNumPoints int `json:"requiredNumPoints,omitempty" description:"The minimum number of points a series needs before it is evaluated. Must be greater than 0 when requireMinPoints is true."`
Thresholds *RuleThresholdData `json:"thresholds,omitempty" description:"The thresholds to evaluate, as a kind and spec. Required when schemaVersion is v2alpha1; derived from op, target and matchType for v1."`
}
func (rc *RuleCondition) SelectedQueryName() string {

View File

@@ -60,44 +60,42 @@ const (
// PostableRule is used to create alerting rule from HTTP api.
type PostableRule struct {
AlertName valuer.DisplayName `json:"alert" required:"true"`
AlertType AlertType `json:"alertType" required:"true"`
Description string `json:"description,omitempty"`
RuleType RuleType `json:"ruleType" required:"true"`
EvalWindow valuer.TextDuration `json:"evalWindow,omitzero"`
Frequency valuer.TextDuration `json:"frequency,omitzero"`
AlertName valuer.DisplayName `json:"alert" required:"true" description:"The display name of the alert rule."`
AlertType AlertType `json:"alertType" required:"true" description:"The signal the rule evaluates: METRIC_BASED_ALERT, TRACES_BASED_ALERT, LOGS_BASED_ALERT, EXCEPTIONS_BASED_ALERT or AI_TRACES_BASED_ALERT."`
Description string `json:"description,omitempty" description:"A human readable description of what the rule monitors."`
RuleType RuleType `json:"ruleType" required:"true" description:"The evaluation strategy: threshold_rule compares against thresholds, promql_rule evaluates a PromQL expression, anomaly_rule scores deviation from seasonal history. Defaults to threshold_rule for builder queries and is forced to promql_rule for promql queries."`
EvalWindow valuer.TextDuration `json:"evalWindow,omitzero" description:"The lookback window as a Go duration such as 5m. Only read when schemaVersion is v1, defaulting to 5m; v2alpha1 rules set it in evaluation.spec instead."`
Frequency valuer.TextDuration `json:"frequency,omitzero" description:"How often the rule is evaluated, as a Go duration such as 1m. Only read when schemaVersion is v1, defaulting to 1m; v2alpha1 rules set it in evaluation.spec instead."`
RuleCondition *RuleCondition `json:"condition" required:"true"`
Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
RuleCondition *RuleCondition `json:"condition" required:"true" description:"The query to evaluate and the thresholds applied to its result."`
Labels map[string]string `json:"labels,omitempty" description:"Labels attached to every alert the rule fires. Keys match [a-zA-Z_.][a-zA-Z0-9_.]*; values may use Go templates such as {{$labels.service_name}}. severity is the conventional routing label."`
Annotations map[string]string `json:"annotations,omitempty" description:"Free text attached to every alert, conventionally summary and description. Values may use Go templates with $value, $threshold and $labels."`
Disabled bool `json:"disabled"`
Disabled bool `json:"disabled" description:"Whether the rule is paused. A disabled rule is neither evaluated nor notifies. Defaults to false."`
// Source captures the source url where rule has been created
Source string `json:"source,omitempty"`
Source string `json:"source,omitempty" description:"The URL of the page the rule was created from. Informational; it is not used for evaluation."`
PreferredChannels []string `json:"preferredChannels,omitempty"`
PreferredChannels []string `json:"preferredChannels,omitempty" description:"The displayName of each notification channel to notify, for schemaVersion v1 rules. Ignored for v2alpha1, where each threshold carries its own channels."`
Version string `json:"version"`
Version string `json:"version" description:"The query payload version. Only v5 is accepted and it is filled in when omitted."`
Evaluation *EvaluationEnvelope `json:"evaluation,omitempty"`
SchemaVersion string `json:"schemaVersion,omitempty"`
Evaluation *EvaluationEnvelope `json:"evaluation,omitempty" description:"The evaluation window, as a kind and spec. Required when schemaVersion is v2alpha1; derived from evalWindow and frequency for v1."`
SchemaVersion string `json:"schemaVersion,omitempty" description:"The rule payload schema: v1 (the default when omitted) uses the flat condition fields, v2alpha1 uses condition.thresholds, evaluation and notificationSettings."`
NotificationSettings *NotificationSettings `json:"notificationSettings,omitempty"`
NotificationSettings *NotificationSettings `json:"notificationSettings,omitempty" description:"Grouping, re-notification and routing behaviour. Required when schemaVersion is v2alpha1; derived from the v1 fields otherwise."`
}
type NotificationSettings struct {
GroupBy []string `json:"groupBy,omitzero"`
Renotify *Renotify `json:"renotify,omitempty"`
UsePolicy bool `json:"usePolicy"`
// NewGroupEvalDelay is the grace period for new series to be excluded from alerts evaluation
NewGroupEvalDelay valuer.TextDuration `json:"newGroupEvalDelay,omitzero"`
GroupBy []string `json:"groupBy,omitzero" description:"Label names whose values group alerts into one notification, such as service.name."`
Renotify *Renotify `json:"renotify,omitempty" description:"Whether and how often to re-send a notification for an alert that stays active."`
UsePolicy bool `json:"usePolicy" description:"When true, alerts are routed by the organisation level notification policies instead of the channels named on the thresholds. Defaults to false."`
NewGroupEvalDelay valuer.TextDuration `json:"newGroupEvalDelay,omitzero" description:"A grace period, as a Go duration such as 2m, during which a newly seen label group is excluded from evaluation. Applies to metric rules only; log and trace rules are never delayed. A group whose values are not yet known is evaluated immediately."`
}
type Renotify struct {
Enabled bool `json:"enabled"`
ReNotifyInterval valuer.TextDuration `json:"interval,omitzero"`
AlertStates []AlertState `json:"alertStates,omitzero"`
Enabled bool `json:"enabled" description:"Whether an active alert is re-sent after interval elapses. When false an alert notifies once per firing."`
ReNotifyInterval valuer.TextDuration `json:"interval,omitzero" description:"The time between repeat notifications, as a Go duration such as 4h. Must be positive when enabled is true."`
AlertStates []AlertState `json:"alertStates,omitzero" description:"The alert states that are re-notified: firing, nodata or both."`
}
func (ns *NotificationSettings) GetAlertManagerNotificationConfig() alertmanagertypes.NotificationConfig {
@@ -625,23 +623,23 @@ func testTemplateParsing(rl *PostableRule) (errs []error) {
// GettableRules has info for all stored rules.
type GettableTestRule struct {
AlertCount int `json:"alertCount"`
Message string `json:"message"`
AlertCount int `json:"alertCount" description:"The number of alerts the test notification produced. Counts every evaluated series, including those that did not cross the threshold."`
Message string `json:"message" description:"A human readable summary of the test evaluation."`
}
type GettableRules struct {
Rules []*GettableRule `json:"rules"`
Rules []*GettableRule `json:"rules" description:"The rules."`
}
// GettableRule has info for an alerting rules.
type GettableRule struct {
Id string `json:"id" required:"true"`
State AlertState `json:"state" required:"true"`
Id string `json:"id" required:"true" description:"The unique identifier of the rule."`
State AlertState `json:"state" required:"true" description:"The current evaluation state: inactive, pending, recovering, firing, nodata or disabled."`
PostableRule
CreatedAt time.Time `json:"createAt" required:"true"`
CreatedBy *string `json:"createBy" nullable:"true"`
UpdatedAt time.Time `json:"updateAt" required:"true"`
UpdatedBy *string `json:"updateBy" nullable:"true"`
CreatedAt time.Time `json:"createAt" required:"true" description:"When the rule was created, in RFC 3339 format."`
CreatedBy *string `json:"createBy" nullable:"true" description:"The identifier of the user who created the rule."`
UpdatedAt time.Time `json:"updateAt" required:"true" description:"When the rule was last updated, in RFC 3339 format."`
UpdatedBy *string `json:"updateBy" nullable:"true" description:"The identifier of the user who last updated the rule."`
}
func (g *GettableRule) MarshalJSON() ([]byte, error) {
@@ -674,8 +672,8 @@ func (g *GettableRule) MarshalJSON() ([]byte, error) {
// PlannedMaintenance and other entities. v1 handlers keep serializing
// GettableRule directly for back-compat with existing SDK / Terraform clients.
type Rule struct {
Id string `json:"id" required:"true"`
State AlertState `json:"state" required:"true"`
Id string `json:"id" required:"true" description:"The unique identifier of the rule."`
State AlertState `json:"state" required:"true" description:"The current evaluation state: inactive, pending, recovering, firing, nodata or disabled."`
PostableRule
types.TimeAuditable
types.UserAuditable

View File

@@ -32,8 +32,8 @@ type Evaluation interface {
}
type RollingWindow struct {
EvalWindow valuer.TextDuration `json:"evalWindow" required:"true"`
Frequency valuer.TextDuration `json:"frequency" required:"true"`
EvalWindow valuer.TextDuration `json:"evalWindow" required:"true" description:"The sliding lookback window each evaluation queries, as a Go duration such as 5m."`
Frequency valuer.TextDuration `json:"frequency" required:"true" description:"How often the rule is evaluated, as a Go duration such as 1m. Must be positive."`
}
func (rollingWindow RollingWindow) Validate() error {
@@ -55,17 +55,17 @@ func (rollingWindow RollingWindow) GetFrequency() valuer.TextDuration {
}
type CumulativeWindow struct {
Schedule CumulativeSchedule `json:"schedule" required:"true"`
Frequency valuer.TextDuration `json:"frequency" required:"true"`
Timezone string `json:"timezone" required:"true"`
Schedule CumulativeSchedule `json:"schedule" required:"true" description:"The boundary the accumulation window starts from. Each evaluation queries from the most recent boundary to now."`
Frequency valuer.TextDuration `json:"frequency" required:"true" description:"How often the rule is evaluated, as a Go duration such as 1m. Must be positive."`
Timezone string `json:"timezone" required:"true" description:"The IANA time zone the schedule is resolved in, such as UTC or Asia/Kolkata."`
}
type CumulativeSchedule struct {
Type ScheduleType `json:"type" required:"true"`
Minute *int `json:"minute,omitempty"` // 0-59, for all types
Hour *int `json:"hour,omitempty"` // 0-23, for daily/weekly/monthly
Day *int `json:"day,omitempty"` // 1-31, for monthly
Weekday *int `json:"weekday,omitempty"` // 0-6 (Sunday=0), for weekly
Type ScheduleType `json:"type" required:"true" description:"How often the window resets: hourly, daily, weekly or monthly."`
Minute *int `json:"minute,omitempty" description:"The minute of the reset boundary, 0 to 59. Required for every type."`
Hour *int `json:"hour,omitempty" description:"The hour of the reset boundary, 0 to 23. Required for daily, weekly and monthly."`
Day *int `json:"day,omitempty" description:"The day of the month of the reset boundary, 1 to 31. Required for monthly."`
Weekday *int `json:"weekday,omitempty" description:"The weekday of the reset boundary, 0 (Sunday) to 6 (Saturday). Required for weekly."`
}
type ScheduleType struct {
@@ -244,8 +244,8 @@ func (cumulativeWindow CumulativeWindow) GetFrequency() valuer.TextDuration {
}
type EvaluationEnvelope struct {
Kind EvaluationKind `json:"kind" required:"true"`
Spec any `json:"spec" required:"true"`
Kind EvaluationKind `json:"kind" required:"true" description:"The kind of evaluation window: rolling slides a fixed lookback, cumulative accumulates from a scheduled reset."`
Spec any `json:"spec" required:"true" description:"The evaluation specification for kind: evalWindow and frequency for rolling, schedule, frequency and timezone for cumulative."`
}
// evaluationRolling is the OpenAPI schema for an EvaluationEnvelope with kind=rolling.

View File

@@ -51,11 +51,11 @@ func (o ListOrder) IsValid() bool {
// ListFilter is the rule listing state shared by the v3 list params and saved views.
type ListFilter struct {
Query string `query:"query" json:"query"`
Query string `query:"query" json:"query" description:"A filter expression over rule name, description, labels and other fields, at most 1024 characters. Empty matches every rule."`
// gin cannot bind a slice of valuer enums; GetAlertStates converts these.
States []string `query:"states" json:"states" nullable:"false"`
Sort ListSort `query:"sort" json:"sort"`
Order ListOrder `query:"order" json:"order"`
States []string `query:"states" json:"states" nullable:"false" description:"Only return rules in these states: firing, pending, recovering, inactive, nodata or disabled. Empty means every state."`
Sort ListSort `query:"sort" json:"sort" description:"The field to sort by: updated_at (the default), created_at, name, state or severity."`
Order ListOrder `query:"order" json:"order" description:"The sort direction: asc or desc (the default)."`
}
// Validate normalizes in place; zero sort/order get the defaults, nil states an empty slice.
@@ -119,8 +119,8 @@ func parseAlertState(raw string) (AlertState, error) {
type ListRulesParams struct {
ListFilter
Limit int `query:"limit"`
Offset int `query:"offset"`
Limit int `query:"limit" description:"The page size, 1 to 200. Defaults to 20 and larger values are clamped to 200."`
Offset int `query:"offset" description:"The number of rules to skip before the page. Defaults to 0."`
}
// Validate normalizes in place; an over-max limit is clamped, not rejected.

View File

@@ -13,14 +13,14 @@ const MaxListLabelPairs = 1000
// ListableRule is the slim per-row shape of the list endpoint; the full rule stays behind get-by-id.
type ListableRule struct {
Id string `json:"id" required:"true"`
State AlertState `json:"state" required:"true"`
AlertName string `json:"alert" required:"true"`
Description string `json:"description,omitempty"`
AlertType AlertType `json:"alertType" required:"true"`
RuleType RuleType `json:"ruleType" required:"true"`
Disabled bool `json:"disabled"`
Labels map[string]string `json:"labels,omitempty"`
Id string `json:"id" required:"true" description:"The unique identifier of the rule."`
State AlertState `json:"state" required:"true" description:"The current evaluation state: inactive, pending, recovering, firing, nodata or disabled."`
AlertName string `json:"alert" required:"true" description:"The display name of the alert rule."`
Description string `json:"description,omitempty" description:"A human readable description of what the rule monitors."`
AlertType AlertType `json:"alertType" required:"true" description:"The signal the rule evaluates: METRIC_BASED_ALERT, TRACES_BASED_ALERT, LOGS_BASED_ALERT, EXCEPTIONS_BASED_ALERT or AI_TRACES_BASED_ALERT."`
RuleType RuleType `json:"ruleType" required:"true" description:"The evaluation strategy: threshold_rule, promql_rule or anomaly_rule."`
Disabled bool `json:"disabled" description:"Whether the rule is paused."`
Labels map[string]string `json:"labels,omitempty" description:"The labels attached to alerts the rule fires."`
types.TimeAuditable
types.UserAuditable
}
@@ -89,15 +89,15 @@ func NewListableRulesFromStorableRules(storedRules []*StorableRule, stateByRuleI
// LabelPair is one distinct label key/value observed on the org's rules.
type LabelPair struct {
Key string `json:"key" required:"true"`
Value string `json:"value" required:"true"`
Key string `json:"key" required:"true" description:"The label name."`
Value string `json:"value" required:"true" description:"The label value."`
}
type ListableRules struct {
Rules []*ListableRule `json:"rules" required:"true" nullable:"false"`
Total int64 `json:"total" required:"true"`
Labels []LabelPair `json:"labels" required:"true" nullable:"false"`
ReservedKeywords []DSLKey `json:"reservedKeywords" required:"true" nullable:"false"`
Rules []*ListableRule `json:"rules" required:"true" nullable:"false" description:"The page of rules matching the filter."`
Total int64 `json:"total" required:"true" description:"The number of rules matching the filter across all pages."`
Labels []LabelPair `json:"labels" required:"true" nullable:"false" description:"Organization-wide label key and value pair suggestions for building filters, independent of the current filter and page, capped at 1000 distinct pairs."`
ReservedKeywords []DSLKey `json:"reservedKeywords" required:"true" nullable:"false" description:"The keys the query filter language reserves, which cannot be used as label names in a query."`
}
func NewListableRules(rules []*ListableRule, total int64, labels []LabelPair) *ListableRules {

View File

@@ -30,8 +30,8 @@ func (ThresholdKind) Enum() []any {
}
type RuleThresholdData struct {
Kind ThresholdKind `json:"kind" required:"true"`
Spec any `json:"spec" required:"true"`
Kind ThresholdKind `json:"kind" required:"true" description:"The kind of threshold. The supported value is basic."`
Spec any `json:"spec" required:"true" description:"The threshold specification for kind; an array of thresholds for basic."`
}
// thresholdBasic is the OpenAPI schema for a RuleThresholdData with kind=basic.
@@ -98,8 +98,8 @@ func (r *RuleThresholdData) UnmarshalJSON(data []byte) error {
}
type RuleReceivers struct {
Channels []string `json:"channels"`
Name string `json:"name"`
Channels []string `json:"channels" description:"The displayName of each notification channel the threshold routes to."`
Name string `json:"name" description:"The threshold name the receivers belong to."`
}
// EvalData are other dependent values used to evaluate the threshold rules.
@@ -133,13 +133,13 @@ type RuleThreshold interface {
}
type BasicRuleThreshold struct {
Name string `json:"name" required:"true"`
TargetValue *float64 `json:"target" required:"true" format:"double"`
TargetUnit string `json:"targetUnit"`
RecoveryTarget *float64 `json:"recoveryTarget" format:"double"`
MatchType MatchType `json:"matchType" required:"true"`
CompareOperator CompareOperator `json:"op" required:"true"`
Channels []string `json:"channels"`
Name string `json:"name" required:"true" description:"The threshold name, conventionally critical, error, warning or info. Alerts carry it as the threshold_name label."`
TargetValue *float64 `json:"target" required:"true" format:"double" description:"The value the query result is compared against, in targetUnit."`
TargetUnit string `json:"targetUnit" description:"The unit of target and recoveryTarget, such as percent or ms. Converted to the query unit before comparison when the two differ; empty means the query unit."`
RecoveryTarget *float64 `json:"recoveryTarget" format:"double" description:"The value at which a firing alert resolves, in targetUnit. Keeps an alert firing between target and recoveryTarget to avoid flapping; null resolves at target."`
MatchType MatchType `json:"matchType" required:"true" description:"Which points in the window must satisfy op. Accepted as at_least_once, all_the_times, on_average, in_total or last, their aliases avg and sum, or the legacy codes as strings, '1' to '5'."`
CompareOperator CompareOperator `json:"op" required:"true" description:"How the value is compared with target. Accepted as above, below, equal, not_equal, above_or_equal, below_or_equal or outside_bounds, their symbols, or the legacy codes as strings, '1' to '7'."`
Channels []string `json:"channels" description:"The displayName of each notification channel alerts crossing this threshold are sent to. Ignored when notificationSettings.usePolicy is true."`
}
type BasicRuleThresholds []BasicRuleThreshold

View File

@@ -35,10 +35,10 @@ type SavedView struct {
types.TimeAuditable
types.UserAuditable
OrgID string `json:"-"`
Name string `json:"name"`
Source Source `json:"source"`
SchemaVersion SchemaVersion `json:"schemaVersion" required:"true"`
Spec SavedViewSpec `json:"spec" required:"true"`
Name string `json:"name" description:"The machine name of the view, a DNS-1123 label unique within the organization. Immutable after creation."`
Source Source `json:"source" description:"The explorer page the view belongs to: traces, logs, metrics, meter or ai_observability."`
SchemaVersion SchemaVersion `json:"schemaVersion" required:"true" description:"The version of the spec shape. The only accepted value is v2."`
Spec SavedViewSpec `json:"spec" required:"true" description:"The display name, panel type, request type, queries and column selection of the view."`
}
type StorableSavedView struct {
@@ -90,22 +90,22 @@ func NewStorableSavedView(view *SavedView) *StorableSavedView {
}
type PostableSavedView struct {
Name string `json:"name"`
GenerateName bool `json:"generateName"`
Source Source `json:"source" required:"true"`
SchemaVersion SchemaVersion `json:"schemaVersion" required:"true"`
Spec SavedViewSpec `json:"spec" required:"true"`
Name string `json:"name" description:"The machine name of the view, a DNS-1123 label (lowercase letters, digits and hyphens, at most 63 characters) unique within the organization. Required unless generateName is true, in which case it must be empty."`
GenerateName bool `json:"generateName" description:"When true the server derives name from spec.displayName and appends a random suffix. Defaults to false."`
Source Source `json:"source" required:"true" description:"The explorer page the view belongs to: traces, logs, metrics, meter or ai_observability. Builder queries inside spec must use the matching signal; meter views use signal metrics with source meter."`
SchemaVersion SchemaVersion `json:"schemaVersion" required:"true" description:"The version of the spec shape. The only accepted value is v2."`
Spec SavedViewSpec `json:"spec" required:"true" description:"The display name, panel type, request type, queries and column selection of the view."`
}
type UpdatableSavedView struct {
Source Source `json:"source" required:"true"`
SchemaVersion SchemaVersion `json:"schemaVersion" required:"true"`
Spec SavedViewSpec `json:"spec" required:"true"`
Source Source `json:"source" required:"true" description:"The explorer page the view belongs to: traces, logs, metrics, meter or ai_observability. The machine name cannot be changed; it is identified by the id in the path."`
SchemaVersion SchemaVersion `json:"schemaVersion" required:"true" description:"The version of the spec shape. The only accepted value is v2."`
Spec SavedViewSpec `json:"spec" required:"true" description:"The full replacement spec. Fields left out are reset, not preserved."`
}
type ListSavedViewsParams struct {
Source Source `query:"source"`
Name string `query:"name"`
Source Source `query:"source" description:"Returns only the views of this explorer page: traces, logs, metrics, meter or ai_observability. Defaults to every source."`
Name string `query:"name" description:"Returns only the view with this exact machine name."`
}
type Source struct {

View File

@@ -20,26 +20,26 @@ var (
// Display holds view-rendering preferences.
type Display struct {
MaxLines int `json:"maxLines"`
FontSize string `json:"fontSize"`
Format string `json:"format"`
Color string `json:"color"`
MaxLines int `json:"maxLines" description:"The number of lines shown per row before the body is truncated in logs list views. 0 leaves the explorer default."`
FontSize string `json:"fontSize" description:"The row font size in list views: small, medium or large. Empty leaves the explorer default."`
Format string `json:"format" description:"The list rendering mode in the logs explorer: raw, table or list. Empty leaves the explorer default."`
Color string `json:"color" description:"A CSS color such as a hex code, shown as the view's color in the explorer view picker; the saved views panel does not use it. Empty leaves the default."`
}
// SavedViewSpec is the typed content of a saved view.
type SavedViewSpec struct {
DisplayName string `json:"displayName" required:"true"`
PanelType PanelType `json:"panelType" required:"true"`
RequestType qbtypes.RequestType `json:"requestType" required:"true"`
Queries []qbtypes.QueryEnvelope `json:"queries" required:"true" nullable:"false" minItems:"1"`
SelectedFields []telemetrytypes.TelemetryFieldKey `json:"selectedFields" nullable:"false"`
Display Display `json:"display"`
DisplayName string `json:"displayName" required:"true" description:"The human readable title shown in the explorer. It may differ from the machine name and need not be unique."`
PanelType PanelType `json:"panelType" required:"true" description:"The explorer panel the view renders as: list, graph, table, value or trace."`
RequestType qbtypes.RequestType `json:"requestType" required:"true" description:"The query_range request type the panel issues: raw for list panels, time_series for graphs, scalar for table and value panels, trace for trace panels."`
Queries []qbtypes.QueryEnvelope `json:"queries" required:"true" nullable:"false" minItems:"1" description:"The query envelopes the view runs, in the v5 query builder shape. At least one is required and the set is validated for the requestType."`
SelectedFields []telemetrytypes.TelemetryFieldKey `json:"selectedFields" nullable:"false" description:"The columns shown in list panels, each a field key with name and optionally signal, fieldContext and fieldDataType. Send explicit columns for list views: an empty array yields no trace columns and only the required log columns. Use an empty array for graph views."`
Display Display `json:"display" description:"Rendering preferences for list panels. All fields are optional."`
}
// SavedViewData is what's persisted as saved view data.
type SavedViewData struct {
SchemaVersion string `json:"schemaVersion" required:"true"`
Spec SavedViewSpec `json:"spec" required:"true"`
SchemaVersion string `json:"schemaVersion" required:"true" description:"The version of the spec shape the stored view was written with."`
Spec SavedViewSpec `json:"spec" required:"true" description:"The stored view spec."`
}
// SchemaVersion has v2 as the only value currently.

View File

@@ -33,16 +33,16 @@ const (
)
type TelemetryFieldKey struct {
Name string `json:"name" validate:"required" required:"true"`
Description string `json:"description,omitempty"`
Unit string `json:"unit,omitempty"`
Name string `json:"name" validate:"required" required:"true" description:"The field name, such as service.name, k8s.pod.name or http.response.status_code. Dots are part of the name, and the names available depend on the signal and the data ingested."`
Description string `json:"description,omitempty" description:"Human-readable summary of the field, present only for well-known fields."`
Unit string `json:"unit,omitempty" description:"Unit of the field values, present only for well-known fields."`
// signal/fieldContext/fieldDataType always serialize (empty included): the empty
// value is a first-class "unspecified / any" selection a client can set, so it
// must round-trip verbatim rather than be dropped. Their Enum()s include the
// empty member so the "" is a valid schema value.
Signal Signal `json:"signal"`
FieldContext FieldContext `json:"fieldContext"`
FieldDataType FieldDataType `json:"fieldDataType"`
Signal Signal `json:"signal" description:"The signal the field belongs to: traces, logs or metrics. Empty means any signal."`
FieldContext FieldContext `json:"fieldContext" description:"Where the field lives: resource, attribute, scope, span, trace, log, body or metric. Empty means unspecified, so the server resolves it."`
FieldDataType FieldDataType `json:"fieldDataType" description:"Data type of the field values: string, bool, int64, float64 or number. Empty means unspecified."`
JSONPlan JSONAccessPlan `json:"-"`
Indexes []TelemetryFieldKeySkipIndex `json:"-"`
@@ -225,10 +225,10 @@ func FieldKeyToMaterializedExistsCondition(key *TelemetryFieldKey, exists bool)
}
type TelemetryFieldValues struct {
StringValues []string `json:"stringValues,omitempty"`
BoolValues []bool `json:"boolValues,omitempty"`
NumberValues []float64 `json:"numberValues,omitempty"`
RelatedValues []string `json:"relatedValues,omitempty"`
StringValues []string `json:"stringValues,omitempty" description:"Distinct string values of the field, up to limit."`
BoolValues []bool `json:"boolValues,omitempty" description:"Distinct boolean values of the field."`
NumberValues []float64 `json:"numberValues,omitempty" description:"Distinct numeric values of the field, up to limit."`
RelatedValues []string `json:"relatedValues,omitempty" description:"Values of the field seen on data that also matches existingQuery, within the startUnixMilli and endUnixMilli bounds. Empty when existingQuery is not set."`
}
func (t *TelemetryFieldValues) NumValues() int {
@@ -236,21 +236,21 @@ func (t *TelemetryFieldValues) NumValues() int {
}
type MetricContext struct {
MetricName string `json:"metricName"`
MetricNamespace string `json:"metricNamespace,omitempty"`
MetricName string `json:"metricName" description:"Restricts keys and values to those seen on this metric. Metrics signal only."`
MetricNamespace string `json:"metricNamespace,omitempty" description:"Restricts keys and values to metrics in this namespace. Metrics signal only."`
}
type FieldKeySelector struct {
StartUnixMilli int64 `json:"startUnixMilli"`
EndUnixMilli int64 `json:"endUnixMilli"`
Signal Signal `json:"signal"`
Source Source `json:"source"`
FieldContext FieldContext `json:"fieldContext"`
FieldDataType FieldDataType `json:"fieldDataType"`
Name string `json:"name"`
SelectorMatchType FieldSelectorMatchType `json:"selectorMatchType"`
Limit int `json:"limit"`
MetricContext *MetricContext `json:"metricContext,omitempty"`
StartUnixMilli int64 `json:"startUnixMilli" description:"Start of the lookup window in Unix milliseconds, rounded down to a 6 hour boundary. Ignored for key lookups and meter lookups; trace and log values apply it at calendar-day granularity; metric attribute values and relatedValues apply it. Zero means no lower bound."`
EndUnixMilli int64 `json:"endUnixMilli" description:"End of the lookup window in Unix milliseconds. Ignored for key lookups, trace and log value lookups and meter lookups; applied to metric attribute values and relatedValues. Zero means no upper bound."`
Signal Signal `json:"signal" description:"The signal to look up keys in: traces, logs or metrics. Empty means every signal."`
Source Source `json:"source" description:"The data source within the signal. Use meter for Cost Meter metrics; empty means the default source."`
FieldContext FieldContext `json:"fieldContext" description:"Restricts keys to one context such as resource or attribute. Applied to trace and log keys; metric attribute keys ignore it. Empty means any context."`
FieldDataType FieldDataType `json:"fieldDataType" description:"Restricts keys to one data type such as string or float64. Applied to trace and log keys; metric attribute keys ignore it. Empty means any type."`
Name string `json:"name" description:"Key name to match; empty matches every key. Matched according to selectorMatchType."`
SelectorMatchType FieldSelectorMatchType `json:"selectorMatchType" description:"How name is matched: exact (case-sensitive equality) or fuzzy (case-insensitive substring, except the meter source where it is case-sensitive and % and _ act as wildcards). The API always uses fuzzy."`
Limit int `json:"limit" description:"Maximum number of keys per signal. Intrinsic and materialized fields are added on top, and an unspecified signal applies the limit to traces, logs and metrics separately, so the result can exceed it."`
MetricContext *MetricContext `json:"metricContext,omitempty" description:"Scopes the lookup to one metric or metric namespace. Metrics signal only."`
}
// MatchesKey reports whether a statically defined key satisfies the selector, so
@@ -276,38 +276,38 @@ func (s *FieldKeySelector) MatchesKey(key *TelemetryFieldKey) bool {
type FieldValueSelector struct {
*FieldKeySelector
ExistingQuery string `json:"existingQuery"`
Value string `json:"value"`
Limit int `json:"limit"`
ExistingQuery string `json:"existingQuery" description:"Filter expression already applied in the explorer. Used to compute relatedValues; empty disables them."`
Value string `json:"value" description:"Substring the returned values must contain, case-insensitive except for the meter source, where it is case-sensitive and % and _ act as wildcards. Empty returns any value."`
Limit int `json:"limit" description:"Maximum number of values to return."`
}
type GettableFieldKeys struct {
Keys map[string][]*TelemetryFieldKey `json:"keys" required:"true"`
Complete bool `json:"complete" required:"true"`
Keys map[string][]*TelemetryFieldKey `json:"keys" required:"true" description:"Matching keys grouped by name. A name maps to several entries when it exists in more than one context or data type."`
Complete bool `json:"complete" required:"true" description:"True when every matching key was returned; false when the result was cut off by limit."`
}
type PostableFieldKeysParams struct {
Signal Signal `query:"signal"`
Source Source `query:"source"`
Limit int `query:"limit"`
StartUnixMilli int64 `query:"startUnixMilli"`
EndUnixMilli int64 `query:"endUnixMilli"`
FieldContext FieldContext `query:"fieldContext"`
FieldDataType FieldDataType `query:"fieldDataType"`
MetricName string `query:"metricName"`
MetricNamespace string `query:"metricNamespace"`
SearchText string `query:"searchText"`
Signal Signal `query:"signal" description:"The signal to look up keys in: traces, logs or metrics. Empty searches every signal."`
Source Source `query:"source" description:"The data source within the signal. Use meter for Cost Meter metrics; empty means the default source."`
Limit int `query:"limit" description:"Maximum number of results per signal. Defaults to 1000 for keys and 50 for values. For keys, intrinsic and materialized fields are added on top, and an empty signal applies the limit to traces, logs and metrics separately, so the response can exceed it."`
StartUnixMilli int64 `query:"startUnixMilli" description:"Start of the lookup window in Unix milliseconds, rounded down to a 6 hour boundary. Ignored for key lookups and meter lookups; trace and log values apply it at calendar-day granularity; metric attribute values and relatedValues apply it. Omit for no lower bound."`
EndUnixMilli int64 `query:"endUnixMilli" description:"End of the lookup window in Unix milliseconds. Ignored for key lookups, trace and log value lookups and meter lookups; applied to metric attribute values and relatedValues. Omit for no upper bound."`
FieldContext FieldContext `query:"fieldContext" description:"Restricts keys to one context: resource, attribute, scope, span, trace, log, body or metric. Applied to trace and log keys; metric attribute keys ignore it. Empty means any context."`
FieldDataType FieldDataType `query:"fieldDataType" description:"Restricts keys to one data type: string, bool, int64, float64 or number. Applied to trace and log keys; metric attribute keys ignore it. Empty means any type."`
MetricName string `query:"metricName" description:"Restricts keys to those seen on this metric. Metrics signal only."`
MetricNamespace string `query:"metricNamespace" description:"Restricts keys to metrics in this namespace. Metrics signal only."`
SearchText string `query:"searchText" description:"Substring to match: against key names for keys, against the values of name for values. Case-insensitive except for the meter source, where it is case-sensitive and % and _ act as wildcards. For keys, a context prefix such as resource.service.name sets fieldContext when it is empty."`
}
type GettableFieldValues struct {
Values *TelemetryFieldValues `json:"values" required:"true"`
Complete bool `json:"complete" required:"true"`
Values *TelemetryFieldValues `json:"values" required:"true" description:"Distinct values of the key, split by data type."`
Complete bool `json:"complete" required:"true" description:"True when every matching value was returned; false when the result was cut off by limit."`
}
type PostableFieldValueParams struct {
PostableFieldKeysParams
Name string `query:"name"`
ExistingQuery string `query:"existingQuery"`
Name string `query:"name" description:"The key whose values to return, such as service.name. A context prefix such as resource.service.name sets fieldContext when it is empty."`
ExistingQuery string `query:"existingQuery" description:"Filter expression already applied in the explorer. When set, relatedValues lists the values seen on data matching it."`
}
func NewFieldKeySelectorFromPostableFieldKeysParams(params PostableFieldKeysParams) *FieldKeySelector {