August 14, 20257 min read

Query Builder Migration

Author:

Author

Query Builder v5 Dashboard&Alert JSON Migration Guide

This guide explains how your widget and alert JSON structures change with Query Builder v5. While the migration occurs automatically for each instance, understanding these changes helps you work with the new format and provides guidance for Terraform users.

Manual editing is error-prone and time-consuming. We recommend waiting for the migration to complete (you'll receive an email notification), then using the updated JSON in your integrations (API, terraform, etc...).

Dashboard Migration

1. Aggregation field changes

In v5, aggregateOperator and aggregateAttribute are combined with expression inside aggregations array. The new query builder supports multiple aggregations in the same query for logs and traces.

For Traces/Logs

Before (v4):

{
  "builder": {
    "queryData": [
      {
        "aggregateOperator": "avg",
        "aggregateAttribute": {
            "key": "duration_nano"
        }
      }
    ]
  }
}

After (v5):

{
  "builder": {
    "queryData": [
      {
        "aggregations": [{
          "expression": "avg(duration_nano)"
        }]
      }
    ]
  }
}

Why are we doing this?

Currently, calculating max, min, and avg values for numeric attributes in logs/traces requires creating three separate queries with identical configurations. This approach is inefficient and impacts performance since it runs multiple queries against the same dataset. The enhanced aggregations feature addresses these limitations to provide a better user experience. Additionally, we plan to introduce conditional aggregations in the near future. For more details, visit https://github.com/SigNoz/signoz/issues/8792.

For Metrics

Before (v4):

{
  "aggregateOperator": "p99",
  "aggregateAttribute": {
    "key": "signoz_latency.bucket"
  },
  "spaceAggregation": "p99"
}

After (v5):

{
  "aggregations": [{
    "metricName": "signoz_latency.bucket",
    "spaceAggregation": "p99",
    "reduceTo": "avg"
  }]
}

Before (v4):

{
  "aggregateOperator": "p99",
  "aggregateAttribute": {
    "key": "signoz_calls_total"
  },
  "spaceAggregation": "sum",
  "timeAggregation": "rate"
}

After (v5):

{
  "aggregations": [{
    "metricName": "signoz_calls_total",
    "spaceAggregation": "sum",
    "timeAggregation": "rate",
    "reduceTo": "avg"
  }]
}

Note about metrics

Currently, multiple aggregations are available only for traces and logs. Support for multiple aggregations on metrics data is planned for future releases. The updated metric aggregation specification eliminates the ambiguous "Aggregate Attribute" terminology, resulting in a cleaner and more intuitive naming.

2. Filter Transformation

Filters change from structured objects to SQL-like expression:

Before (v4):

{
  "filters": {
    "items": [
      {
        "key": {
          "key": "service.name",
          "type": "resource"
        },
        "op": "=",
        "value": "{{.service.name}}"
      },
      {
        "key": {
          "key": "httpMethod",
          "type": "tag"
        },
        "op": "exists"
      },
      {
        "key": {
          "key": "spanKind",
          "type": "tag"
        },
        "op": "=",
        "value": "Server"
      }
    ],
    "op": "AND"
  }
}

After (v5):

{
  "filter": {
    "expression": "(serviceName = $service.name AND httpMethod EXISTS AND spanKind = 'Server')"
  }
}

Why are we doing this?

  • We're making this change for several reasons: to add OR operator support, enable complex searches combining AND, OR, and parentheses, allow in-place editing, and support copying search expressions. The original structured approach to filtering hasn't scaled well over time, so we're addressing it now.
  • As filtering needs have grown more complex, the current query builder has developed numerous UX issues that can be frustrating for users.

3. Group By Changes

Remains unchanged for dashboards.

4. Order By Changes

Before (v4):

{
  "orderBy": [{
    "columnName": "#SIGNOZ_VALUE",
    "order": "desc"
  }]
}

After (v5):

{
  "orderBy": [{
    "columnName": "count()",
    "order": "desc"
  }]
}

The #SIGNOZ_VALUE placeholder is replaced with either the actual aggregation expression or, in list views, the field used for ordering results. This cryptic placeholder is only meaningful to the feature's original developer. Previously, we haven't prioritized Infrastructure as Code (IaC) for managing SigNoz, but this change is a step in that direction.

5. Variable Format Changes

Variables are standardized to use the $ prefix:

  • {{.service.name}}$service.name
  • {{deployment.environment}}$deployment.environment
  • [[variable]]$variable

Why are we doing this?

Single naming scheme to work with variables across the product

6. Having Clause Changes

The having clause transforms from an array of conditions to a expression:

Before (v4):

{
  "having": [
    {
      "columnName": "#SIGNOZ_VALUE",
      "op": ">",
      "value": 50
    },
    {
      "columnName": "#SIGNOZ_VALUE",
      "op": "<",
      "value": 100
    }
  ]
}

After (v5):

{
  "having": {
    "expression": "count() > 50 AND count() < 100"
  }
}

Why are we doing this?

This cryptic placeholder is only understandable to the original developer. We're removing it in favor of a more intuitive approach. For empty having clauses, the field can simply remain unset.

7. Function Migration

Functions now use named argument format:

Before (v4):

{
  "functions": [
    {
      "name": "cutOffMin",
      "args": [2]
    }
  ]
}

After (v5):

{
  "functions": [
    {
      "name": "cutOffMin",
      "args": [
        {"name": "threshold", "value": 2}
      ]
    }
  ]
}

8. Dashboard Widget Example

HTTP Endpoint Monitoring Widget - Before (v4):

{
  "query": {
    "builder": {
      "queryData": [{
        "aggregateOperator": "count",
        "dataSource": "traces",
        "filters": {
          "items": [
            {"key": {"key": "serviceName"}, "op": "=", "value": "{{.service.name}}"},
            {"key": {"key": "httpMethod"}, "op": "exists"},
            {"key": {"key": "spanKind"}, "op": "=", "value": "Server"}
          ],
          "op": "AND"
        },
        "groupBy": [
          {"key": "httpRoute", "type": "tag"},
          {"key": "httpMethod", "type": "tag"}
        ],
        "orderBy": [{"columnName": "#SIGNOZ_VALUE", "order": "desc"}],
        "queryName": "A"
      }]
    },
    "queryType": "builder"
  }
}

After (v5):

{
  "query": {
    "builder": {
      "queryData": [{
        "aggregations": [{"expression": "count()"}],
        "dataSource": "traces",
        "filter": {
          "expression": "(serviceName = $service.name AND httpMethod EXISTS AND spanKind = 'Server')"
        },
        "groupBy": [
          {"key": "httpRoute"},
          {"key": "httpMethod"}
        ],
        "orderBy": [{"columnName": "count()", "order": "desc"}],
        "queryName": "A"
      }]
    },
    "queryType": "builder"
  }
}

Alert Migration

1. Alert Query Structure Changes

The alerts is moving from separate query type objects to a unified queries array:

Fields Removed in v5

  • aggregateOperator and aggregateAttribute
  • filters (replaced by filter.expression)
  • builderQueries, promQueries, chQueries (unified into queries)
  • temporality, timeAggregation, spaceAggregation (moved into aggregations for metrics)

New Fields in v5

  • queries array containing all query types
  • type field to identify query type
  • spec object containing query specification
  • signal field for builder queries (traces, logs, metrics)
  • filter.expression for SQL-like filter syntax

Before (v4):

{
  "condition": {
    "compositeQuery": {
      "builderQueries": {
        "A": {
          "aggregateOperator": "count",
          "dataSource": "traces",
          "filters": {}
        },
        "B": {
          "aggregateOperator": "avg",
          "dataSource": "traces",
          "filters": {}
        }
      },
      "promQueries": {
        "C": {
          "query": "rate(http_requests[5m])"
        }
      },
      "queryFormulas": {
        "F1": {
          "expression": "A/B"
        }
      }
    }
  }
}

After (v5):

{
  "condition": {
    "compositeQuery": {
      "queries": [
        {
          "type": "builder_query",
          "spec": {
            "name": "A",
            "signal": "traces",
            "aggregations": [{"expression": "count()"}],
            "filter": {}
          }
        },
        {
          "type": "builder_query",
          "spec": {
            "name": "B",
            "signal": "traces",
            "aggregations": [{"expression": "avg(durationNano)"}],
            "filter": {}
          }
        },
        {
          "type": "builder_formula",
          "spec": {
            "name": "F1",
            "expression": "A/B"
          }
        }
      ]
    }
  }
}

2. Error Rate Alert Example

Before (v4):

{
  "condition": {
    "compositeQuery": {
      "builderQueries": {
        "A": {
          "aggregateOperator": "count",
          "dataSource": "traces",
          "filters": {
            "items": [
              {"key": {"key": "serviceName"}, "op": "=", "value": "{{.service.name}}"},
              {"key": {"key": "statusCode"}, "op": "=", "value": "STATUS_CODE_ERROR"}
            ],
            "op": "AND"
          }
        },
        "B": {
          "aggregateOperator": "count",
          "dataSource": "traces",
          "filters": {
            "items": [
              {"key": {"key": "serviceName"}, "op": "=", "value": "{{.service.name}}"}
            ]
          }
        }
      },
      "queryFormulas": {
        "F1": {
          "expression": "A/B*100",
          "legend": "error percentage"
        }
      }
    }
  }
}

After (v5):

{
  "condition": {
    "compositeQuery": {
      "queries": [
        {
          "type": "builder_query",
          "spec": {
            "name": "A",
            "signal": "traces",
            "aggregations": [{"expression": "count()"}],
            "filter": {
              "expression": "serviceName = $service.name AND statusCode = 'STATUS_CODE_ERROR'"
            }
          }
        },
        {
          "type": "builder_query",
          "spec": {
            "name": "B",
            "signal": "traces",
            "aggregations": [{"expression": "count()"}],
            "filter": {
              "expression": "serviceName = $service.name"
            }
          }
        },
        {
          "type": "builder_formula",
          "spec": {
            "name": "F1",
            "expression": "A/B*100",
            "legend": "error percentage"
          }
        }
      ]
    }
  }
}

Was this page helpful?