Compare commits

...

1 Commits

Author SHA1 Message Date
Srikanth Chekuri
7edab87e52 chore: query builder support for dashboard template variables 2024-06-17 00:34:59 +05:30
3 changed files with 183 additions and 1 deletions

View File

@@ -301,6 +301,8 @@ func (aH *APIHandler) RegisterQueryRangeV3Routes(router *mux.Router, am *AuthMid
subRouter.HandleFunc("/query_range", am.ViewAccess(aH.QueryRangeV3)).Methods(http.MethodPost)
subRouter.HandleFunc("/query_range/format", am.ViewAccess(aH.QueryRangeV3Format)).Methods(http.MethodPost)
router.HandleFunc("/dashboards/variables/query", am.ViewAccess(aH.queryDashboardVarsV3)).Methods(http.MethodPost)
// live logs
subRouter.HandleFunc("/logs/livetail", am.ViewAccess(aH.liveTailLogs)).Methods(http.MethodGet)
}
@@ -688,7 +690,6 @@ func (aH *APIHandler) deleteDashboard(w http.ResponseWriter, r *http.Request) {
}
func (aH *APIHandler) queryDashboardVars(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("query")
if query == "" {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("query is required")}, nil)
@@ -3020,6 +3021,30 @@ func (aH *APIHandler) QueryRangeV3Format(w http.ResponseWriter, r *http.Request)
aH.Respond(w, queryRangeParams)
}
func (aH *APIHandler) queryDashboardVarsV3(w http.ResponseWriter, r *http.Request) {
req, err := parseTemplateVariableValueRequest(r)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
switch req.QueryType {
case v3.QueryTypeBuilder:
query, err := prepareQueryForTemplateVariableValue(req)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
vars, err := aH.reader.QueryDashboardVars(r.Context(), query)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
aH.Respond(w, vars)
case v3.QueryTypePromQL:
}
}
func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.QueryRangeParamsV3, w http.ResponseWriter, r *http.Request) {
var result []*v3.Result

View File

@@ -837,6 +837,140 @@ func parseAggregateAttributeRequest(r *http.Request) (*v3.AggregateAttributeRequ
return &req, nil
}
func parseTemplateVariableValueRequest(r *http.Request) (*v3.TmplVariableValueRequest, error) {
var req v3.TmplVariableValueRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
if err := req.QueryType.Validate(); err != nil {
return nil, err
}
if req.QueryType == v3.QueryTypeBuilder {
if req.Builder == nil {
return nil, fmt.Errorf("builder is required")
}
if err := req.Builder.AggregateAttribute.Validate(); err != nil {
return nil, err
}
if req.Builder.Filters != nil {
if err := req.Builder.Filters.Validate(); err != nil {
return nil, err
}
}
} else if req.QueryType == v3.QueryTypePromQL {
if req.PromQL == nil {
return nil, fmt.Errorf("promql is required")
}
if len(req.PromQL.Query) == 0 {
return nil, fmt.Errorf("query is required")
}
} else {
return nil, fmt.Errorf("invalid query type")
}
formatterVars := make(map[string]interface{})
for k, v := range req.Variables {
if req.QueryType == v3.QueryTypeBuilder {
formatterVars[k] = utils.ClickHouseFormattedValue(v)
} else if req.QueryType == v3.QueryTypePromQL {
formatterVars[k] = metrics.PromFormattedValue(v)
}
}
req.Variables = formatterVars
return &req, nil
}
func prepareQueryForMetrics(req *v3.TmplVariableValueRequest) (string, error) {
var metricName string
for _, item := range req.Builder.Filters.Items {
if item.Key.Key == "__name__" {
metricName = item.Value.(string)
}
}
var conditions []string
conditions = append(conditions, fmt.Sprintf("metric_name = %s", metricName))
if req.Builder.Filters != nil && len(req.Builder.Filters.Items) != 0 {
for _, item := range req.Builder.Filters.Items {
toFormat := item.Value
op := v3.FilterOperator(strings.ToLower(strings.TrimSpace(string(item.Operator))))
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
toFormat = fmt.Sprintf("%%%s%%", toFormat)
}
fmtVal := utils.ClickHouseFormattedValue(toFormat)
switch op {
case v3.FilterOperatorEqual:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", item.Key.Key, fmtVal))
case v3.FilterOperatorNotEqual:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') != %s", item.Key.Key, fmtVal))
case v3.FilterOperatorIn:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') IN %s", item.Key.Key, fmtVal))
case v3.FilterOperatorNotIn:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') NOT IN %s", item.Key.Key, fmtVal))
case v3.FilterOperatorLike:
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorNotLike:
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorRegex:
conditions = append(conditions, fmt.Sprintf("match(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorNotRegex:
conditions = append(conditions, fmt.Sprintf("not match(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorGreaterThan:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') > %s", item.Key.Key, fmtVal))
case v3.FilterOperatorGreaterThanOrEq:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') >= %s", item.Key.Key, fmtVal))
case v3.FilterOperatorLessThan:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') < %s", item.Key.Key, fmtVal))
case v3.FilterOperatorLessThanOrEq:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') <= %s", item.Key.Key, fmtVal))
case v3.FilterOperatorContains:
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorNotContains:
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorExists:
conditions = append(conditions, fmt.Sprintf("has(JSONExtractKeys(labels), '%s')", item.Key.Key))
case v3.FilterOperatorNotExists:
conditions = append(conditions, fmt.Sprintf("not has(JSONExtractKeys(labels), '%s')", item.Key.Key))
default:
return "", fmt.Errorf("unsupported filter operator")
}
}
}
whereClause := strings.Join(conditions, " AND ")
for k, v := range req.Variables {
whereClause = strings.Replace(whereClause, fmt.Sprintf("{{.%s}}", k), fmt.Sprint(v), -1)
whereClause = strings.Replace(whereClause, fmt.Sprintf("{{%s}}", k), fmt.Sprint(v), -1)
whereClause = strings.Replace(whereClause, fmt.Sprintf("[[%s]]", k), fmt.Sprint(v), -1)
whereClause = strings.Replace(whereClause, fmt.Sprintf("$%s", k), fmt.Sprint(v), -1)
}
label := req.Builder.AggregateAttribute.Key
query := fmt.Sprintf("SELECT JSONExtractString(labels, '%s') as %s FROM %s.%s WHERE %s GROUP BY %s",
label, label, baseconstants.SIGNOZ_METRIC_DBNAME, baseconstants.SIGNOZ_TIMESERIES_v4_1DAY_TABLENAME, whereClause, label)
return query, nil
}
func prepareQueryForTemplateVariableValue(req *v3.TmplVariableValueRequest) (string, error) {
var query string
var err error
switch req.Builder.DataSource {
case v3.DataSourceMetrics:
query, err = prepareQueryForMetrics(req)
case v3.DataSourceLogs:
case v3.DataSourceTraces:
}
return query, err
}
func parseFilterAttributeKeyRequest(r *http.Request) (*v3.FilterAttributeKeyRequest, error) {
var req v3.FilterAttributeKeyRequest

View File

@@ -275,6 +275,29 @@ func (q AttributeKeyDataType) Validate() error {
}
}
type TmplVariableBuilderRequest struct {
DataSource DataSource `json:"dataSource"`
AggregateAttribute AttributeKey `json:"aggregateAttribute"`
Filters *FilterSet `json:"filters,omitempty"`
}
type TmplVariablePromQLRequest struct {
Query string `json:"query"`
}
type TmplVariableValueRequest struct {
QueryType QueryType `json:"queryType"`
Builder *TmplVariableBuilderRequest `json:"builder,omitempty"`
PromQL *TmplVariablePromQLRequest `json:"promql,omitempty"`
Variables map[string]interface{} `json:"variables,omitempty"`
}
type TmplVariableValueResponse struct {
StringAttributeValues []string `json:"stringAttributeValues"`
NumberAttributeValues []interface{} `json:"numberAttributeValues"`
BoolAttributeValues []bool `json:"boolAttributeValues"`
}
// FilterAttributeValueRequest is a request to fetch possible attribute values
// for a selected aggregate operator, aggregate attribute, filter attribute key
// and search text.