mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 04:35:58 +08:00
feat: ability to save and retrieve the explorer queries (#2284)
This commit is contained in:
parent
6defa0ac8b
commit
c5d7d9d134
@ -27,7 +27,9 @@ import (
|
|||||||
|
|
||||||
baseapp "go.signoz.io/signoz/pkg/query-service/app"
|
baseapp "go.signoz.io/signoz/pkg/query-service/app"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/explorer"
|
||||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/signoz/pkg/query-service/healthcheck"
|
"go.signoz.io/signoz/pkg/query-service/healthcheck"
|
||||||
basealm "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
basealm "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||||
@ -84,6 +86,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
localDB, err := dashboards.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH)
|
localDB, err := dashboards.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH)
|
||||||
|
explorer.InitWithDSN(constants.RELATIONAL_DATASOURCE_PATH)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
155
pkg/query-service/app/explorer/db.go
Normal file
155
pkg/query-service/app/explorer/db.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package explorer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *sqlx.DB
|
||||||
|
|
||||||
|
type ExplorerQuery struct {
|
||||||
|
UUID string `json:"uuid" db:"uuid"`
|
||||||
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||||
|
SourcePage string `json:"source_page" db:"source_page"`
|
||||||
|
// 0 - false, 1 - true
|
||||||
|
IsView int8 `json:"is_view" db:"is_view"`
|
||||||
|
Data string `json:"data" db:"data"`
|
||||||
|
ExtraData string `json:"extra_data" db:"extra_data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitWithDSN sets up setting up the connection pool global variable.
|
||||||
|
func InitWithDSN(dataSourceName string) (*sqlx.DB, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
db, err = sqlx.Open("sqlite3", dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tableSchema := `CREATE TABLE IF NOT EXISTS explorer_queries (
|
||||||
|
uuid TEXT PRIMARY KEY,
|
||||||
|
created_at datetime NOT NULL,
|
||||||
|
updated_at datetime NOT NULL,
|
||||||
|
source_page TEXT NOT NULL,
|
||||||
|
is_view INTEGER NOT NULL,
|
||||||
|
data TEXT NOT NULL,
|
||||||
|
extra_data TEXT
|
||||||
|
);`
|
||||||
|
|
||||||
|
_, err = db.Exec(tableSchema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error in creating explorer queries table: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitWithDB(sqlDB *sqlx.DB) {
|
||||||
|
db = sqlDB
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetQueries() ([]*v3.ExplorerQuery, error) {
|
||||||
|
var queries []ExplorerQuery
|
||||||
|
err := db.Select(&queries, "SELECT * FROM explorer_queries")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error in getting explorer queries: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var explorerQueries []*v3.ExplorerQuery
|
||||||
|
for _, query := range queries {
|
||||||
|
var compositeQuery v3.CompositeQuery
|
||||||
|
err = json.Unmarshal([]byte(query.Data), &compositeQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error in unmarshalling explorer query data: %s", err.Error())
|
||||||
|
}
|
||||||
|
explorerQueries = append(explorerQueries, &v3.ExplorerQuery{
|
||||||
|
UUID: query.UUID,
|
||||||
|
SourcePage: query.SourcePage,
|
||||||
|
CompositeQuery: &compositeQuery,
|
||||||
|
IsView: query.IsView,
|
||||||
|
ExtraData: query.ExtraData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return explorerQueries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateQuery(query v3.ExplorerQuery) (string, error) {
|
||||||
|
data, err := json.Marshal(query.CompositeQuery)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error in marshalling explorer query data: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid_ := query.UUID
|
||||||
|
|
||||||
|
if uuid_ == "" {
|
||||||
|
uuid_ = uuid.New().String()
|
||||||
|
}
|
||||||
|
createdAt := time.Now()
|
||||||
|
updatedAt := time.Now()
|
||||||
|
|
||||||
|
_, err = db.Exec(
|
||||||
|
"INSERT INTO explorer_queries (uuid, created_at, updated_at, source_page, is_view, data, extra_data) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
uuid_,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
query.SourcePage,
|
||||||
|
query.IsView,
|
||||||
|
data,
|
||||||
|
query.ExtraData,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error in creating explorer query: %s", err.Error())
|
||||||
|
}
|
||||||
|
return uuid_, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetQuery(uuid_ string) (*v3.ExplorerQuery, error) {
|
||||||
|
var query ExplorerQuery
|
||||||
|
err := db.Get(&query, "SELECT * FROM explorer_queries WHERE uuid = ?", uuid_)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error in getting explorer query: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var compositeQuery v3.CompositeQuery
|
||||||
|
err = json.Unmarshal([]byte(query.Data), &compositeQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error in unmarshalling explorer query data: %s", err.Error())
|
||||||
|
}
|
||||||
|
return &v3.ExplorerQuery{
|
||||||
|
UUID: query.UUID,
|
||||||
|
SourcePage: query.SourcePage,
|
||||||
|
CompositeQuery: &compositeQuery,
|
||||||
|
IsView: query.IsView,
|
||||||
|
ExtraData: query.ExtraData,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateQuery(uuid_ string, query v3.ExplorerQuery) error {
|
||||||
|
data, err := json.Marshal(query.CompositeQuery)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error in marshalling explorer query data: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAt := time.Now()
|
||||||
|
|
||||||
|
_, err = db.Exec("UPDATE explorer_queries SET updated_at = ?, source_page = ?, is_view = ?, data = ?, extra_data = ? WHERE uuid = ?",
|
||||||
|
updatedAt, query.SourcePage, query.IsView, data, query.ExtraData, uuid_)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error in updating explorer query: %s", err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteQuery(uuid_ string) error {
|
||||||
|
_, err := db.Exec("DELETE FROM explorer_queries WHERE uuid = ?", uuid_)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error in deleting explorer query: %s", err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -19,6 +19,7 @@ import (
|
|||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"github.com/prometheus/prometheus/promql"
|
"github.com/prometheus/prometheus/promql"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/explorer"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/logs"
|
"go.signoz.io/signoz/pkg/query-service/app/logs"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/metrics"
|
"go.signoz.io/signoz/pkg/query-service/app/metrics"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/parser"
|
"go.signoz.io/signoz/pkg/query-service/app/parser"
|
||||||
@ -280,6 +281,12 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *AuthMiddleware) {
|
|||||||
router.HandleFunc("/api/v1/variables/query", am.ViewAccess(aH.queryDashboardVars)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/variables/query", am.ViewAccess(aH.queryDashboardVars)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v2/variables/query", am.ViewAccess(aH.queryDashboardVarsV2)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v2/variables/query", am.ViewAccess(aH.queryDashboardVarsV2)).Methods(http.MethodPost)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v1/explorer/queries", am.ViewAccess(aH.getExplorerQueries)).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc("/api/v1/explorer/queries", am.EditAccess(aH.createExplorerQueries)).Methods(http.MethodPost)
|
||||||
|
router.HandleFunc("/api/v1/explorer/queries/{queryId}", am.ViewAccess(aH.getExplorerQuery)).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc("/api/v1/explorer/queries/{queryId}", am.EditAccess(aH.updateExplorerQuery)).Methods(http.MethodPut)
|
||||||
|
router.HandleFunc("/api/v1/explorer/queries/{queryId}", am.EditAccess(aH.deleteExplorerQuery)).Methods(http.MethodDelete)
|
||||||
|
|
||||||
router.HandleFunc("/api/v1/feedback", am.OpenAccess(aH.submitFeedback)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/feedback", am.OpenAccess(aH.submitFeedback)).Methods(http.MethodPost)
|
||||||
// router.HandleFunc("/api/v1/get_percentiles", aH.getApplicationPercentiles).Methods(http.MethodGet)
|
// router.HandleFunc("/api/v1/get_percentiles", aH.getApplicationPercentiles).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v1/services", am.ViewAccess(aH.getServices)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/services", am.ViewAccess(aH.getServices)).Methods(http.MethodPost)
|
||||||
@ -2253,6 +2260,82 @@ func (aH *APIHandler) logAggregate(w http.ResponseWriter, r *http.Request) {
|
|||||||
aH.WriteJSON(w, r, res)
|
aH.WriteJSON(w, r, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) getExplorerQueries(w http.ResponseWriter, r *http.Request) {
|
||||||
|
queries, err := explorer.GetQueries()
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aH.Respond(w, queries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) createExplorerQueries(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var query v3.ExplorerQuery
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&query)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// validate the query
|
||||||
|
if err := query.Validate(); err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uuid, err := explorer.CreateQuery(query)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aH.Respond(w, uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) getExplorerQuery(w http.ResponseWriter, r *http.Request) {
|
||||||
|
queryID := mux.Vars(r)["queryId"]
|
||||||
|
query, err := explorer.GetQuery(queryID)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aH.Respond(w, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) updateExplorerQuery(w http.ResponseWriter, r *http.Request) {
|
||||||
|
queryID := mux.Vars(r)["queryId"]
|
||||||
|
var query v3.ExplorerQuery
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&query)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// validate the query
|
||||||
|
if err := query.Validate(); err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = explorer.UpdateQuery(queryID, query)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aH.Respond(w, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) deleteExplorerQuery(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
queryID := mux.Vars(r)["queryId"]
|
||||||
|
err := explorer.DeleteQuery(queryID)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aH.Respond(w, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) autocompleteAggregateAttributes(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) autocompleteAggregateAttributes(w http.ResponseWriter, r *http.Request) {
|
||||||
var response *v3.AggregateAttributeResponse
|
var response *v3.AggregateAttributeResponse
|
||||||
req, err := parseAggregateAttributeRequest(r)
|
req, err := parseAggregateAttributeRequest(r)
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/soheilhy/cmux"
|
"github.com/soheilhy/cmux"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/clickhouseReader"
|
"go.signoz.io/signoz/pkg/query-service/app/clickhouseReader"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/explorer"
|
||||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/signoz/pkg/query-service/dao"
|
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||||
@ -77,6 +78,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
localDB, err := dashboards.InitDB(constants.RELATIONAL_DATASOURCE_PATH)
|
localDB, err := dashboards.InitDB(constants.RELATIONAL_DATASOURCE_PATH)
|
||||||
|
explorer.InitWithDSN(constants.RELATIONAL_DATASOURCE_PATH)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -3,6 +3,8 @@ package v3
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataSource string
|
type DataSource string
|
||||||
@ -95,6 +97,19 @@ func (a AggregateOperator) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequireAttribute returns true if the aggregate operator requires an attribute
|
||||||
|
// to be specified.
|
||||||
|
func (a AggregateOperator) RequireAttribute() bool {
|
||||||
|
switch a {
|
||||||
|
case AggregateOperatorNoOp,
|
||||||
|
AggregateOpeatorCount,
|
||||||
|
AggregateOperatorCountDistinct:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ReduceToOperator string
|
type ReduceToOperator string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -245,11 +260,35 @@ type PromQuery struct {
|
|||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PromQuery) Validate() error {
|
||||||
|
if p == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Query == "" {
|
||||||
|
return fmt.Errorf("query is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ClickHouseQuery struct {
|
type ClickHouseQuery struct {
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ClickHouseQuery) Validate() error {
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Query == "" {
|
||||||
|
return fmt.Errorf("query is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type CompositeQuery struct {
|
type CompositeQuery struct {
|
||||||
BuilderQueries map[string]*BuilderQuery `json:"builderQueries,omitempty"`
|
BuilderQueries map[string]*BuilderQuery `json:"builderQueries,omitempty"`
|
||||||
ClickHouseQueries map[string]*ClickHouseQuery `json:"chQueries,omitempty"`
|
ClickHouseQueries map[string]*ClickHouseQuery `json:"chQueries,omitempty"`
|
||||||
@ -258,6 +297,50 @@ type CompositeQuery struct {
|
|||||||
QueryType QueryType `json:"queryType"`
|
QueryType QueryType `json:"queryType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CompositeQuery) Validate() error {
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BuilderQueries == nil && c.ClickHouseQueries == nil && c.PromQueries == nil {
|
||||||
|
return fmt.Errorf("composite query must contain at least one query")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BuilderQueries != nil {
|
||||||
|
for name, query := range c.BuilderQueries {
|
||||||
|
if err := query.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("builder query %s is invalid: %w", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ClickHouseQueries != nil {
|
||||||
|
for name, query := range c.ClickHouseQueries {
|
||||||
|
if err := query.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("clickhouse query %s is invalid: %w", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.PromQueries != nil {
|
||||||
|
for name, query := range c.PromQueries {
|
||||||
|
if err := query.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("prom query %s is invalid: %w", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.PanelType.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("panel type is invalid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.QueryType.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("query type is invalid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type BuilderQuery struct {
|
type BuilderQuery struct {
|
||||||
QueryName string `json:"queryName"`
|
QueryName string `json:"queryName"`
|
||||||
DataSource DataSource `json:"dataSource"`
|
DataSource DataSource `json:"dataSource"`
|
||||||
@ -276,11 +359,61 @@ type BuilderQuery struct {
|
|||||||
SelectColumns []string `json:"selectColumns,omitempty"`
|
SelectColumns []string `json:"selectColumns,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BuilderQuery) Validate() error {
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if b.QueryName == "" {
|
||||||
|
return fmt.Errorf("query name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// if expression is same as query name, it's a simple builder query and not a formula
|
||||||
|
// formula involves more than one data source, aggregate operator, etc.
|
||||||
|
if b.QueryName == b.Expression {
|
||||||
|
if err := b.DataSource.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("data source is invalid: %w", err)
|
||||||
|
}
|
||||||
|
if err := b.AggregateOperator.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("aggregate operator is invalid: %w", err)
|
||||||
|
}
|
||||||
|
if b.AggregateAttribute == "" && b.AggregateOperator.RequireAttribute() {
|
||||||
|
return fmt.Errorf("aggregate attribute is required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Filters != nil {
|
||||||
|
if err := b.Filters.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("filters are invalid: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b.GroupBy != nil {
|
||||||
|
for _, groupBy := range b.GroupBy {
|
||||||
|
if groupBy == "" {
|
||||||
|
return fmt.Errorf("group by cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b.Expression == "" {
|
||||||
|
return fmt.Errorf("expression is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type FilterSet struct {
|
type FilterSet struct {
|
||||||
Operator string `json:"op,omitempty"`
|
Operator string `json:"op,omitempty"`
|
||||||
Items []FilterItem `json:"items"`
|
Items []FilterItem `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FilterSet) Validate() error {
|
||||||
|
if f == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if f.Operator != "" && f.Operator != "AND" && f.Operator != "OR" {
|
||||||
|
return fmt.Errorf("operator must be AND or OR")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type FilterItem struct {
|
type FilterItem struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Value interface{} `json:"value"`
|
Value interface{} `json:"value"`
|
||||||
@ -323,3 +456,32 @@ type Point struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
Value float64 `json:"value"`
|
Value float64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExploreQuery is a query for the explore page
|
||||||
|
// It is a composite query with a source page name
|
||||||
|
// The source page name is used to identify the page that initiated the query
|
||||||
|
// The source page could be "traces", "logs", "metrics" or "dashboards", "alerts" etc.
|
||||||
|
type ExplorerQuery struct {
|
||||||
|
UUID string `json:"uuid,omitempty"`
|
||||||
|
SourcePage string `json:"sourcePage"`
|
||||||
|
CompositeQuery *CompositeQuery `json:"compositeQuery"`
|
||||||
|
// ExtraData is JSON encoded data used by frontend to store additional data
|
||||||
|
ExtraData string `json:"extraData"`
|
||||||
|
// 0 - false, 1 - true; this is int8 because sqlite doesn't support bool
|
||||||
|
IsView int8 `json:"isView"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eq *ExplorerQuery) Validate() error {
|
||||||
|
if eq.IsView != 0 && eq.IsView != 1 {
|
||||||
|
return fmt.Errorf("isView must be 0 or 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if eq.CompositeQuery == nil {
|
||||||
|
return fmt.Errorf("composite query is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if eq.UUID == "" {
|
||||||
|
eq.UUID = uuid.New().String()
|
||||||
|
}
|
||||||
|
return eq.CompositeQuery.Validate()
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user