Compare commits

...

1 Commits

Author SHA1 Message Date
nityanandagohain
20f3e78f1d fix: add ai_observability to saved views 2026-08-24 23:22:52 +05:30
6 changed files with 85 additions and 5 deletions

View File

@@ -8010,6 +8010,7 @@ components:
- logs
- metrics
- meter
- ai_observability
type: string
SavedviewtypesUpdatableSavedView:
properties:

View File

@@ -9021,6 +9021,7 @@ export enum SavedviewtypesSourceDTO {
logs = 'logs',
metrics = 'metrics',
meter = 'meter',
ai_observability = 'ai_observability',
}
export interface SavedviewtypesSavedViewSpecDTO {
display?: SavedviewtypesDisplayDTO;

View File

@@ -23,10 +23,11 @@ var (
const savedViewNameSuffixLen = 8
var (
SourceTraces = Source{valuer.NewString("traces")}
SourceLogs = Source{valuer.NewString("logs")}
SourceMetrics = Source{valuer.NewString("metrics")}
SourceMeter = Source{valuer.NewString("meter")}
SourceTraces = Source{valuer.NewString("traces")}
SourceLogs = Source{valuer.NewString("logs")}
SourceMetrics = Source{valuer.NewString("metrics")}
SourceMeter = Source{valuer.NewString("meter")}
SourceAIObservability = Source{valuer.NewString("ai_observability")}
)
type SavedView struct {
@@ -117,12 +118,13 @@ func (Source) Enum() []any {
SourceLogs,
SourceMetrics,
SourceMeter,
SourceAIObservability,
}
}
func (s Source) Validate() error {
switch s {
case SourceTraces, SourceLogs, SourceMetrics, SourceMeter:
case SourceTraces, SourceLogs, SourceMetrics, SourceMeter, SourceAIObservability:
return nil
default:
return errors.NewInvalidInputf(ErrCodeSavedViewInvalidInput, "invalid source: %s", s.StringValue())

View File

@@ -39,6 +39,7 @@ func TestSourceValidate(t *testing.T) {
{name: "logs", source: SourceLogs},
{name: "metrics", source: SourceMetrics},
{name: "meter", source: SourceMeter},
{name: "ai_observability", source: SourceAIObservability},
{name: "unknown is rejected", source: Source{valuer.NewString("bogus")}, expectError: true},
}

View File

@@ -173,6 +173,21 @@ func TestSavedViewSpecValidate(t *testing.T) {
},
expectError: false,
},
{
name: "builder_ai_query is valid",
spec: SavedViewSpec{
DisplayName: "My View",
PanelType: PanelTypeList,
RequestType: qbtypes.RequestTypeRaw,
Queries: []qbtypes.QueryEnvelope{{
Type: qbtypes.QueryTypeBuilderAI,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Signal: telemetrytypes.SignalTraces,
},
}},
},
expectError: false,
},
{
name: "graph panel query with no aggregation is still rejected",
spec: SavedViewSpec{

View File

@@ -592,6 +592,66 @@ def test_saved_view_lifecycle(
assert response.status_code == HTTPStatus.NOT_FOUND
def test_ai_observability_view_with_builder_ai_query_roundtrip(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
"""builder_ai_query implies the traces signal -- the spec is sent without
one and must read back with signal pinned to "traces"."""
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
headers = {"Authorization": f"Bearer {token}"}
response = requests.post(
signoz.self.host_configs["8080"].get(BASE_URL),
json={
"name": "ai-observability-overview",
"generateName": False,
"source": "ai_observability",
"schemaVersion": "v2",
"spec": {
"displayName": "ai-observability-overview",
"requestType": "scalar",
"queries": [{"type": "builder_ai_query", "spec": {"name": "A", "aggregations": [{"expression": "count()"}], "disabled": False, "legend": ""}}],
"selectedFields": [],
"panelType": "table",
"display": {"maxLines": 0, "fontSize": "", "format": "", "color": ""},
},
},
headers=headers,
timeout=5,
)
assert response.status_code == HTTPStatus.CREATED, response.text
view_id = response.json()["data"]["id"]
try:
response = requests.get(
signoz.self.host_configs["8080"].get(f"{BASE_URL}/{view_id}"),
headers=headers,
timeout=5,
)
assert response.status_code == HTTPStatus.OK, response.text
got = response.json()["data"]
assert got["source"] == "ai_observability"
assert got["spec"]["queries"][0]["type"] == "builder_ai_query"
assert got["spec"]["queries"][0]["spec"]["signal"] == "traces"
response = requests.get(
signoz.self.host_configs["8080"].get(BASE_URL),
params={"source": "ai_observability"},
headers=headers,
timeout=5,
)
assert response.status_code == HTTPStatus.OK, response.text
assert {v["name"] for v in response.json()["data"]} == {"ai-observability-overview"}
finally:
requests.delete(
signoz.self.host_configs["8080"].get(f"{BASE_URL}/{view_id}"),
headers=headers,
timeout=5,
)
def test_empty_name_derives_a_slug_from_display_name(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument