mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-13 01:11:29 +08:00
chore: update saved views endpoints (#3314)
This commit is contained in:
parent
893fcfa6ee
commit
25fc7b83ec
@ -1,26 +1,32 @@
|
||||
package explorer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
)
|
||||
|
||||
var db *sqlx.DB
|
||||
|
||||
type ExplorerQuery struct {
|
||||
type SavedView struct {
|
||||
UUID string `json:"uuid" db:"uuid"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Category string `json:"category" db:"category"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
CreatedBy string `json:"created_by" db:"created_by"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
UpdatedBy string `json:"updated_by" db:"updated_by"`
|
||||
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"`
|
||||
Tags string `json:"tags" db:"tags"`
|
||||
Data string `json:"data" db:"data"`
|
||||
ExtraData string `json:"extra_data" db:"extra_data"`
|
||||
}
|
||||
|
||||
// InitWithDSN sets up setting up the connection pool global variable.
|
||||
@ -32,19 +38,23 @@ func InitWithDSN(dataSourceName string) (*sqlx.DB, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tableSchema := `CREATE TABLE IF NOT EXISTS explorer_queries (
|
||||
tableSchema := `CREATE TABLE IF NOT EXISTS saved_views (
|
||||
uuid TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
created_by TEXT,
|
||||
updated_at datetime NOT NULL,
|
||||
updated_by TEXT,
|
||||
source_page TEXT NOT NULL,
|
||||
is_view INTEGER NOT NULL,
|
||||
tags TEXT,
|
||||
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 nil, fmt.Errorf("error in creating saved views table: %s", err.Error())
|
||||
}
|
||||
|
||||
return db, nil
|
||||
@ -54,38 +64,79 @@ func InitWithDB(sqlDB *sqlx.DB) {
|
||||
db = sqlDB
|
||||
}
|
||||
|
||||
func GetQueries() ([]*v3.ExplorerQuery, error) {
|
||||
var queries []ExplorerQuery
|
||||
err := db.Select(&queries, "SELECT * FROM explorer_queries")
|
||||
func GetViews() ([]*v3.SavedView, error) {
|
||||
var views []SavedView
|
||||
err := db.Select(&views, "SELECT * FROM saved_views")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error in getting explorer queries: %s", err.Error())
|
||||
return nil, fmt.Errorf("error in getting saved views: %s", err.Error())
|
||||
}
|
||||
|
||||
var explorerQueries []*v3.ExplorerQuery
|
||||
for _, query := range queries {
|
||||
var savedViews []*v3.SavedView
|
||||
for _, view := range views {
|
||||
var compositeQuery v3.CompositeQuery
|
||||
err = json.Unmarshal([]byte(query.Data), &compositeQuery)
|
||||
err = json.Unmarshal([]byte(view.Data), &compositeQuery)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error in unmarshalling explorer query data: %s", err.Error())
|
||||
return nil, fmt.Errorf("error in unmarshalling explorer query data: %s", err.Error())
|
||||
}
|
||||
explorerQueries = append(explorerQueries, &v3.ExplorerQuery{
|
||||
UUID: query.UUID,
|
||||
SourcePage: query.SourcePage,
|
||||
savedViews = append(savedViews, &v3.SavedView{
|
||||
UUID: view.UUID,
|
||||
Name: view.Name,
|
||||
Category: view.Category,
|
||||
CreatedAt: view.CreatedAt,
|
||||
CreatedBy: view.CreatedBy,
|
||||
UpdatedAt: view.UpdatedAt,
|
||||
UpdatedBy: view.UpdatedBy,
|
||||
Tags: strings.Split(view.Tags, ","),
|
||||
SourcePage: view.SourcePage,
|
||||
CompositeQuery: &compositeQuery,
|
||||
IsView: query.IsView,
|
||||
ExtraData: query.ExtraData,
|
||||
ExtraData: view.ExtraData,
|
||||
})
|
||||
}
|
||||
return explorerQueries, nil
|
||||
return savedViews, nil
|
||||
}
|
||||
|
||||
func CreateQuery(query v3.ExplorerQuery) (string, error) {
|
||||
data, err := json.Marshal(query.CompositeQuery)
|
||||
func GetViewsForFilters(sourcePage string, name string, category string) ([]*v3.SavedView, error) {
|
||||
var views []SavedView
|
||||
var err error
|
||||
if len(category) == 0 {
|
||||
err = db.Select(&views, "SELECT * FROM saved_views WHERE source_page = ? AND name LIKE ?", sourcePage, "%"+name+"%")
|
||||
} else {
|
||||
err = db.Select(&views, "SELECT * FROM saved_views WHERE source_page = ? AND category LIKE ? AND name LIKE ?", sourcePage, "%"+category+"%", "%"+name+"%")
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error in marshalling explorer query data: %s", err.Error())
|
||||
return nil, fmt.Errorf("error in getting saved views: %s", err.Error())
|
||||
}
|
||||
|
||||
uuid_ := query.UUID
|
||||
var savedViews []*v3.SavedView
|
||||
for _, view := range views {
|
||||
var compositeQuery v3.CompositeQuery
|
||||
err = json.Unmarshal([]byte(view.Data), &compositeQuery)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in unmarshalling explorer query data: %s", err.Error())
|
||||
}
|
||||
savedViews = append(savedViews, &v3.SavedView{
|
||||
UUID: view.UUID,
|
||||
Name: view.Name,
|
||||
CreatedAt: view.CreatedAt,
|
||||
CreatedBy: view.CreatedBy,
|
||||
UpdatedAt: view.UpdatedAt,
|
||||
UpdatedBy: view.UpdatedBy,
|
||||
Tags: strings.Split(view.Tags, ","),
|
||||
SourcePage: view.SourcePage,
|
||||
CompositeQuery: &compositeQuery,
|
||||
ExtraData: view.ExtraData,
|
||||
})
|
||||
}
|
||||
return savedViews, nil
|
||||
}
|
||||
|
||||
func CreateView(ctx context.Context, view v3.SavedView) (string, error) {
|
||||
data, err := json.Marshal(view.CompositeQuery)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error in marshalling explorer query data: %s", err.Error())
|
||||
}
|
||||
|
||||
uuid_ := view.UUID
|
||||
|
||||
if uuid_ == "" {
|
||||
uuid_ = uuid.New().String()
|
||||
@ -93,63 +144,101 @@ func CreateQuery(query v3.ExplorerQuery) (string, error) {
|
||||
createdAt := time.Now()
|
||||
updatedAt := time.Now()
|
||||
|
||||
email, err := getEmailFromJwt(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
createBy := email
|
||||
updatedBy := email
|
||||
|
||||
_, err = db.Exec(
|
||||
"INSERT INTO explorer_queries (uuid, created_at, updated_at, source_page, is_view, data, extra_data) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
"INSERT INTO saved_views (uuid, name, category, created_at, created_by, updated_at, updated_by, source_page, tags, data, extra_data) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
uuid_,
|
||||
view.Name,
|
||||
view.Category,
|
||||
createdAt,
|
||||
createBy,
|
||||
updatedAt,
|
||||
query.SourcePage,
|
||||
query.IsView,
|
||||
updatedBy,
|
||||
view.SourcePage,
|
||||
strings.Join(view.Tags, ","),
|
||||
data,
|
||||
query.ExtraData,
|
||||
view.ExtraData,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error in creating explorer query: %s", err.Error())
|
||||
return "", fmt.Errorf("error in creating saved view: %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_)
|
||||
func GetView(uuid_ string) (*v3.SavedView, error) {
|
||||
var view SavedView
|
||||
err := db.Get(&view, "SELECT * FROM saved_views WHERE uuid = ?", uuid_)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error in getting explorer query: %s", err.Error())
|
||||
return nil, fmt.Errorf("error in getting saved view: %s", err.Error())
|
||||
}
|
||||
|
||||
var compositeQuery v3.CompositeQuery
|
||||
err = json.Unmarshal([]byte(query.Data), &compositeQuery)
|
||||
err = json.Unmarshal([]byte(view.Data), &compositeQuery)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error in unmarshalling explorer query data: %s", err.Error())
|
||||
return nil, fmt.Errorf("error in unmarshalling explorer query data: %s", err.Error())
|
||||
}
|
||||
return &v3.ExplorerQuery{
|
||||
UUID: query.UUID,
|
||||
SourcePage: query.SourcePage,
|
||||
return &v3.SavedView{
|
||||
UUID: view.UUID,
|
||||
Name: view.Name,
|
||||
Category: view.Category,
|
||||
CreatedAt: view.CreatedAt,
|
||||
CreatedBy: view.CreatedBy,
|
||||
UpdatedAt: view.UpdatedAt,
|
||||
UpdatedBy: view.UpdatedBy,
|
||||
SourcePage: view.SourcePage,
|
||||
Tags: strings.Split(view.Tags, ","),
|
||||
CompositeQuery: &compositeQuery,
|
||||
IsView: query.IsView,
|
||||
ExtraData: query.ExtraData,
|
||||
ExtraData: view.ExtraData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UpdateQuery(uuid_ string, query v3.ExplorerQuery) error {
|
||||
data, err := json.Marshal(query.CompositeQuery)
|
||||
func UpdateView(ctx context.Context, uuid_ string, view v3.SavedView) error {
|
||||
data, err := json.Marshal(view.CompositeQuery)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error in marshalling explorer query data: %s", err.Error())
|
||||
return fmt.Errorf("error in marshalling explorer query data: %s", err.Error())
|
||||
}
|
||||
|
||||
email, err := getEmailFromJwt(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedAt := time.Now()
|
||||
updatedBy := email
|
||||
|
||||
_, 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_)
|
||||
_, err = db.Exec("UPDATE saved_views SET updated_at = ?, updated_by = ?, name = ?, category = ?, source_page = ?, tags = ?, data = ?, extra_data = ? WHERE uuid = ?",
|
||||
updatedAt, updatedBy, view.Name, view.Category, view.SourcePage, strings.Join(view.Tags, ","), data, view.ExtraData, uuid_)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error in updating explorer query: %s", err.Error())
|
||||
return fmt.Errorf("error in updating saved view: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteQuery(uuid_ string) error {
|
||||
_, err := db.Exec("DELETE FROM explorer_queries WHERE uuid = ?", uuid_)
|
||||
func DeleteView(uuid_ string) error {
|
||||
_, err := db.Exec("DELETE FROM saved_views WHERE uuid = ?", uuid_)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error in deleting explorer query: %s", err.Error())
|
||||
return fmt.Errorf("error in deleting explorer query: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEmailFromJwt(ctx context.Context) (string, error) {
|
||||
jwt, err := auth.ExtractJwtFromContext(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
claims, err := auth.ParseJWT(jwt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return claims["email"].(string), nil
|
||||
}
|
||||
|
@ -338,11 +338,11 @@ 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/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/explorer/views", am.ViewAccess(aH.getSavedViews)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/explorer/views", am.ViewAccess(aH.createSavedViews)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.ViewAccess(aH.getSavedView)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.ViewAccess(aH.updateSavedView)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.ViewAccess(aH.deleteSavedView)).Methods(http.MethodDelete)
|
||||
|
||||
router.HandleFunc("/api/v1/feedback", am.OpenAccess(aH.submitFeedback)).Methods(http.MethodPost)
|
||||
// router.HandleFunc("/api/v1/get_percentiles", aH.getApplicationPercentiles).Methods(http.MethodGet)
|
||||
@ -2522,8 +2522,13 @@ func (ah *APIHandler) createLogsPipeline(w http.ResponseWriter, r *http.Request)
|
||||
ah.Respond(w, res)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getExplorerQueries(w http.ResponseWriter, r *http.Request) {
|
||||
queries, err := explorer.GetQueries()
|
||||
func (aH *APIHandler) getSavedViews(w http.ResponseWriter, r *http.Request) {
|
||||
// get sourcePage, name, and category from the query params
|
||||
sourcePage := r.URL.Query().Get("sourcePage")
|
||||
name := r.URL.Query().Get("name")
|
||||
category := r.URL.Query().Get("category")
|
||||
|
||||
queries, err := explorer.GetViewsForFilters(sourcePage, name, category)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
@ -2531,19 +2536,20 @@ func (aH *APIHandler) getExplorerQueries(w http.ResponseWriter, r *http.Request)
|
||||
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)
|
||||
func (aH *APIHandler) createSavedViews(w http.ResponseWriter, r *http.Request) {
|
||||
var view v3.SavedView
|
||||
err := json.NewDecoder(r.Body).Decode(&view)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
// validate the query
|
||||
if err := query.Validate(); err != nil {
|
||||
if err := view.Validate(); err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
uuid, err := explorer.CreateQuery(query)
|
||||
ctx := auth.AttachJwtToContext(context.Background(), r)
|
||||
uuid, err := explorer.CreateView(ctx, view)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
@ -2552,44 +2558,45 @@ func (aH *APIHandler) createExplorerQueries(w http.ResponseWriter, r *http.Reque
|
||||
aH.Respond(w, uuid)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getExplorerQuery(w http.ResponseWriter, r *http.Request) {
|
||||
queryID := mux.Vars(r)["queryId"]
|
||||
query, err := explorer.GetQuery(queryID)
|
||||
func (aH *APIHandler) getSavedView(w http.ResponseWriter, r *http.Request) {
|
||||
viewID := mux.Vars(r)["viewId"]
|
||||
view, err := explorer.GetView(viewID)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, query)
|
||||
aH.Respond(w, view)
|
||||
}
|
||||
|
||||
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)
|
||||
func (aH *APIHandler) updateSavedView(w http.ResponseWriter, r *http.Request) {
|
||||
viewID := mux.Vars(r)["viewId"]
|
||||
var view v3.SavedView
|
||||
err := json.NewDecoder(r.Body).Decode(&view)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
// validate the query
|
||||
if err := query.Validate(); err != nil {
|
||||
if err := view.Validate(); err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
err = explorer.UpdateQuery(queryID, query)
|
||||
ctx := auth.AttachJwtToContext(context.Background(), r)
|
||||
err = explorer.UpdateView(ctx, viewID, view)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, query)
|
||||
aH.Respond(w, view)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) deleteExplorerQuery(w http.ResponseWriter, r *http.Request) {
|
||||
func (aH *APIHandler) deleteSavedView(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
queryID := mux.Vars(r)["queryId"]
|
||||
err := explorer.DeleteQuery(queryID)
|
||||
viewID := mux.Vars(r)["viewId"]
|
||||
err := explorer.DeleteView(viewID)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
|
@ -638,24 +638,26 @@ func (p *Point) UnmarshalJSON(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// ExploreQuery is a query for the explore page
|
||||
// It is a composite query with a source page name
|
||||
// SavedView is a saved query for the explore page
|
||||
// It is a composite query with a source page name and user defined tags
|
||||
// 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 {
|
||||
// The source page could be "traces", "logs", "metrics".
|
||||
type SavedView struct {
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
SourcePage string `json:"sourcePage"`
|
||||
Tags []string `json:"tags"`
|
||||
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")
|
||||
}
|
||||
func (eq *SavedView) Validate() error {
|
||||
|
||||
if eq.CompositeQuery == nil {
|
||||
return fmt.Errorf("composite query is required")
|
||||
|
Loading…
x
Reference in New Issue
Block a user