chore: update saved views endpoints (#3314)

This commit is contained in:
Srikanth Chekuri 2023-08-25 09:22:46 +05:30 committed by GitHub
parent 893fcfa6ee
commit 25fc7b83ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 185 additions and 87 deletions

View File

@ -1,24 +1,30 @@
package explorer package explorer
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"go.signoz.io/signoz/pkg/query-service/auth"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
) )
var db *sqlx.DB var db *sqlx.DB
type ExplorerQuery struct { type SavedView struct {
UUID string `json:"uuid" db:"uuid"` 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"` 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"` 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"` SourcePage string `json:"source_page" db:"source_page"`
// 0 - false, 1 - true Tags string `json:"tags" db:"tags"`
IsView int8 `json:"is_view" db:"is_view"`
Data string `json:"data" db:"data"` Data string `json:"data" db:"data"`
ExtraData string `json:"extra_data" db:"extra_data"` ExtraData string `json:"extra_data" db:"extra_data"`
} }
@ -32,19 +38,23 @@ func InitWithDSN(dataSourceName string) (*sqlx.DB, error) {
return nil, err return nil, err
} }
tableSchema := `CREATE TABLE IF NOT EXISTS explorer_queries ( tableSchema := `CREATE TABLE IF NOT EXISTS saved_views (
uuid TEXT PRIMARY KEY, uuid TEXT PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
created_at datetime NOT NULL, created_at datetime NOT NULL,
created_by TEXT,
updated_at datetime NOT NULL, updated_at datetime NOT NULL,
updated_by TEXT,
source_page TEXT NOT NULL, source_page TEXT NOT NULL,
is_view INTEGER NOT NULL, tags TEXT,
data TEXT NOT NULL, data TEXT NOT NULL,
extra_data TEXT extra_data TEXT
);` );`
_, err = db.Exec(tableSchema) _, err = db.Exec(tableSchema)
if err != nil { 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 return db, nil
@ -54,38 +64,79 @@ func InitWithDB(sqlDB *sqlx.DB) {
db = sqlDB db = sqlDB
} }
func GetQueries() ([]*v3.ExplorerQuery, error) { func GetViews() ([]*v3.SavedView, error) {
var queries []ExplorerQuery var views []SavedView
err := db.Select(&queries, "SELECT * FROM explorer_queries") err := db.Select(&views, "SELECT * FROM saved_views")
if err != nil { 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 var savedViews []*v3.SavedView
for _, query := range queries { for _, view := range views {
var compositeQuery v3.CompositeQuery var compositeQuery v3.CompositeQuery
err = json.Unmarshal([]byte(query.Data), &compositeQuery) err = json.Unmarshal([]byte(view.Data), &compositeQuery)
if err != nil { 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{ savedViews = append(savedViews, &v3.SavedView{
UUID: query.UUID, UUID: view.UUID,
SourcePage: query.SourcePage, 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, CompositeQuery: &compositeQuery,
IsView: query.IsView, ExtraData: view.ExtraData,
ExtraData: query.ExtraData,
}) })
} }
return explorerQueries, nil return savedViews, nil
} }
func CreateQuery(query v3.ExplorerQuery) (string, error) { func GetViewsForFilters(sourcePage string, name string, category string) ([]*v3.SavedView, error) {
data, err := json.Marshal(query.CompositeQuery) 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 { 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_ == "" { if uuid_ == "" {
uuid_ = uuid.New().String() uuid_ = uuid.New().String()
@ -93,63 +144,101 @@ func CreateQuery(query v3.ExplorerQuery) (string, error) {
createdAt := time.Now() createdAt := time.Now()
updatedAt := time.Now() updatedAt := time.Now()
email, err := getEmailFromJwt(ctx)
if err != nil {
return "", err
}
createBy := email
updatedBy := email
_, err = db.Exec( _, 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_, uuid_,
view.Name,
view.Category,
createdAt, createdAt,
createBy,
updatedAt, updatedAt,
query.SourcePage, updatedBy,
query.IsView, view.SourcePage,
strings.Join(view.Tags, ","),
data, data,
query.ExtraData, view.ExtraData,
) )
if err != nil { 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 return uuid_, nil
} }
func GetQuery(uuid_ string) (*v3.ExplorerQuery, error) { func GetView(uuid_ string) (*v3.SavedView, error) {
var query ExplorerQuery var view SavedView
err := db.Get(&query, "SELECT * FROM explorer_queries WHERE uuid = ?", uuid_) err := db.Get(&view, "SELECT * FROM saved_views WHERE uuid = ?", uuid_)
if err != nil { 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 var compositeQuery v3.CompositeQuery
err = json.Unmarshal([]byte(query.Data), &compositeQuery) err = json.Unmarshal([]byte(view.Data), &compositeQuery)
if err != nil { 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{ return &v3.SavedView{
UUID: query.UUID, UUID: view.UUID,
SourcePage: query.SourcePage, 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, CompositeQuery: &compositeQuery,
IsView: query.IsView, ExtraData: view.ExtraData,
ExtraData: query.ExtraData,
}, nil }, nil
} }
func UpdateQuery(uuid_ string, query v3.ExplorerQuery) error { func UpdateView(ctx context.Context, uuid_ string, view v3.SavedView) error {
data, err := json.Marshal(query.CompositeQuery) data, err := json.Marshal(view.CompositeQuery)
if err != nil { 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() updatedAt := time.Now()
updatedBy := email
_, err = db.Exec("UPDATE explorer_queries SET updated_at = ?, source_page = ?, is_view = ?, data = ?, extra_data = ? WHERE uuid = ?", _, err = db.Exec("UPDATE saved_views SET updated_at = ?, updated_by = ?, name = ?, category = ?, source_page = ?, tags = ?, data = ?, extra_data = ? WHERE uuid = ?",
updatedAt, query.SourcePage, query.IsView, data, query.ExtraData, uuid_) updatedAt, updatedBy, view.Name, view.Category, view.SourcePage, strings.Join(view.Tags, ","), data, view.ExtraData, uuid_)
if err != nil { 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 return nil
} }
func DeleteQuery(uuid_ string) error { func DeleteView(uuid_ string) error {
_, err := db.Exec("DELETE FROM explorer_queries WHERE uuid = ?", uuid_) _, err := db.Exec("DELETE FROM saved_views WHERE uuid = ?", uuid_)
if err != nil { 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 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
}

View File

@ -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/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/views", am.ViewAccess(aH.getSavedViews)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/explorer/queries", am.EditAccess(aH.createExplorerQueries)).Methods(http.MethodPost) router.HandleFunc("/api/v1/explorer/views", am.ViewAccess(aH.createSavedViews)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/explorer/queries/{queryId}", am.ViewAccess(aH.getExplorerQuery)).Methods(http.MethodGet) router.HandleFunc("/api/v1/explorer/views/{viewId}", am.ViewAccess(aH.getSavedView)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/explorer/queries/{queryId}", am.EditAccess(aH.updateExplorerQuery)).Methods(http.MethodPut) router.HandleFunc("/api/v1/explorer/views/{viewId}", am.ViewAccess(aH.updateSavedView)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/explorer/queries/{queryId}", am.EditAccess(aH.deleteExplorerQuery)).Methods(http.MethodDelete) 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/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)
@ -2522,8 +2522,13 @@ func (ah *APIHandler) createLogsPipeline(w http.ResponseWriter, r *http.Request)
ah.Respond(w, res) ah.Respond(w, res)
} }
func (aH *APIHandler) getExplorerQueries(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getSavedViews(w http.ResponseWriter, r *http.Request) {
queries, err := explorer.GetQueries() // 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 { if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil) RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
return return
@ -2531,19 +2536,20 @@ func (aH *APIHandler) getExplorerQueries(w http.ResponseWriter, r *http.Request)
aH.Respond(w, queries) aH.Respond(w, queries)
} }
func (aH *APIHandler) createExplorerQueries(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) createSavedViews(w http.ResponseWriter, r *http.Request) {
var query v3.ExplorerQuery var view v3.SavedView
err := json.NewDecoder(r.Body).Decode(&query) err := json.NewDecoder(r.Body).Decode(&view)
if err != nil { if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return return
} }
// validate the query // 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) RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return return
} }
uuid, err := explorer.CreateQuery(query) ctx := auth.AttachJwtToContext(context.Background(), r)
uuid, err := explorer.CreateView(ctx, view)
if err != nil { if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil) RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
return return
@ -2552,44 +2558,45 @@ func (aH *APIHandler) createExplorerQueries(w http.ResponseWriter, r *http.Reque
aH.Respond(w, uuid) aH.Respond(w, uuid)
} }
func (aH *APIHandler) getExplorerQuery(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getSavedView(w http.ResponseWriter, r *http.Request) {
queryID := mux.Vars(r)["queryId"] viewID := mux.Vars(r)["viewId"]
query, err := explorer.GetQuery(queryID) view, err := explorer.GetView(viewID)
if err != nil { if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil) RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
return return
} }
aH.Respond(w, query) aH.Respond(w, view)
} }
func (aH *APIHandler) updateExplorerQuery(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) updateSavedView(w http.ResponseWriter, r *http.Request) {
queryID := mux.Vars(r)["queryId"] viewID := mux.Vars(r)["viewId"]
var query v3.ExplorerQuery var view v3.SavedView
err := json.NewDecoder(r.Body).Decode(&query) err := json.NewDecoder(r.Body).Decode(&view)
if err != nil { if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return return
} }
// validate the query // 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) RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return return
} }
err = explorer.UpdateQuery(queryID, query) ctx := auth.AttachJwtToContext(context.Background(), r)
err = explorer.UpdateView(ctx, viewID, view)
if err != nil { if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil) RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
return 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"] viewID := mux.Vars(r)["viewId"]
err := explorer.DeleteQuery(queryID) err := explorer.DeleteView(viewID)
if err != nil { if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil) RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
return return

View File

@ -638,24 +638,26 @@ func (p *Point) UnmarshalJSON(data []byte) error {
return err return err
} }
// ExploreQuery is a query for the explore page // SavedView is a saved query for the explore page
// It is a composite query with a source page name // 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 name is used to identify the page that initiated the query
// The source page could be "traces", "logs", "metrics" or "dashboards", "alerts" etc. // The source page could be "traces", "logs", "metrics".
type ExplorerQuery struct { type SavedView struct {
UUID string `json:"uuid,omitempty"` 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"` SourcePage string `json:"sourcePage"`
Tags []string `json:"tags"`
CompositeQuery *CompositeQuery `json:"compositeQuery"` CompositeQuery *CompositeQuery `json:"compositeQuery"`
// ExtraData is JSON encoded data used by frontend to store additional data // ExtraData is JSON encoded data used by frontend to store additional data
ExtraData string `json:"extraData"` 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 { func (eq *SavedView) Validate() error {
if eq.IsView != 0 && eq.IsView != 1 {
return fmt.Errorf("isView must be 0 or 1")
}
if eq.CompositeQuery == nil { if eq.CompositeQuery == nil {
return fmt.Errorf("composite query is required") return fmt.Errorf("composite query is required")