openreplay/backend/pkg/analytics/cards/model.go

194 lines
5.5 KiB
Go

package cards
import (
"github.com/go-playground/validator/v10"
"strings"
"time"
)
// CardBase Common fields for the Card entity
type CardBase struct {
Name string `json:"name" validate:"required"`
IsPublic bool `json:"isPublic" validate:"omitempty"`
DefaultConfig map[string]any `json:"defaultConfig"`
Config map[string]any `json:"config"`
Thumbnail *string `json:"thumbnail" validate:"omitempty,url"`
MetricType string `json:"metricType" validate:"required,oneof=timeseries table funnel"`
MetricOf string `json:"metricOf" validate:"required,oneof=session_count user_count"`
MetricFormat string `json:"metricFormat" validate:"required,oneof=default percentage"`
ViewType string `json:"viewType" validate:"required,oneof=line_chart table_view"`
MetricValue []string `json:"metricValue" validate:"omitempty"`
SessionID *int64 `json:"sessionId" validate:"omitempty"`
Series []CardSeriesBase `json:"series" validate:"required,dive"`
}
// Card Fields specific to database operations
type Card struct {
CardBase
ProjectID int64 `json:"projectId" validate:"required"`
UserID int64 `json:"userId" validate:"required"`
CardID int64 `json:"cardId"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
EditedAt *time.Time `json:"edited_at,omitempty"`
}
type CardSeriesBase struct {
Name string `json:"name" validate:"required"`
CreatedAt time.Time `json:"createdAt" validate:"omitempty"`
DeletedAt *time.Time `json:"deletedAt" validate:"omitempty"`
Index int64 `json:"index" validate:"required"`
Filter SeriesFilter `json:"filter"`
}
type CardSeries struct {
SeriesID int64 `json:"seriesId" validate:"omitempty"`
MetricID int64 `json:"metricId" validate:"omitempty"`
CardSeriesBase
}
type SeriesFilter struct {
EventOrder string `json:"eventOrder" validate:"required,oneof=then or and"`
Filters []FilterItem `json:"filters"`
}
type FilterItem struct {
Type string `json:"type" validate:"required"`
Operator string `json:"operator" validate:"required"`
Source string `json:"source" validate:"required"`
SourceOperator string `json:"sourceOperator" validate:"required"`
Value []string `json:"value" validate:"required,dive,required"`
IsEvent bool `json:"isEvent"`
}
// CardCreateRequest Fields required for creating a card (from the frontend)
type CardCreateRequest struct {
CardBase
}
type CardGetResponse struct {
Card
Series []CardSeries `json:"series"`
}
type CardUpdateRequest struct {
CardBase
}
type GetCardsResponse struct {
Cards []Card `json:"cards"`
}
type GetCardsResponsePaginated struct {
Cards []Card `json:"cards"`
Total int `json:"total"`
}
/************************************************************
* CardListFilter and Sorter
*/
// Supported filters, fields, and orders
var (
SupportedFilterKeys = map[string]bool{
"name": true,
"metric_type": true,
"dashboard_ids": true,
}
SupportedSortFields = map[string]string{
"name": "m.name",
"created_at": "m.created_at",
"metric_type": "m.metric_type",
}
SupportedSortOrders = map[string]bool{
"asc": true,
"desc": true,
}
)
// CardListFilter holds filtering criteria for listing cards.
type CardListFilter struct {
// Keys: "name" (string), "metric_type" (string), "dashboard_ids" ([]int)
Filters map[string]interface{} `validate:"supportedFilters"`
}
// CardListSort holds sorting criteria.
type CardListSort struct {
Field string `validate:"required,supportedSortField"`
Order string `validate:"required,supportedSortOrder"`
}
// Validator singleton
var validate *validator.Validate
func GetValidator() *validator.Validate {
if validate == nil {
validate = validator.New()
// Register custom validations
_ = validate.RegisterValidation("supportedFilters", supportedFiltersValidator)
_ = validate.RegisterValidation("supportedSortField", supportedSortFieldValidator)
_ = validate.RegisterValidation("supportedSortOrder", supportedSortOrderValidator)
}
return validate
}
func ValidateStruct(obj interface{}) error {
return GetValidator().Struct(obj)
}
// Custom validations
func supportedFiltersValidator(fl validator.FieldLevel) bool {
filters, ok := fl.Field().Interface().(map[string]interface{})
if !ok {
return false
}
for k := range filters {
if !SupportedFilterKeys[k] {
return false
}
}
return true
}
func supportedSortFieldValidator(fl validator.FieldLevel) bool {
field := strings.ToLower(fl.Field().String())
_, ok := SupportedSortFields[field]
return ok
}
func supportedSortOrderValidator(fl validator.FieldLevel) bool {
order := strings.ToLower(fl.Field().String())
return SupportedSortOrders[order]
}
// Filter helpers
func (f *CardListFilter) GetNameFilter() *string {
if val, ok := f.Filters["name"].(string); ok && val != "" {
return &val
}
return nil
}
func (f *CardListFilter) GetMetricTypeFilter() *string {
if val, ok := f.Filters["metric_type"].(string); ok && val != "" {
return &val
}
return nil
}
func (f *CardListFilter) GetDashboardIDs() []int {
if val, ok := f.Filters["dashboard_ids"].([]int); ok && len(val) > 0 {
return val
}
return nil
}
// Sort helpers
func (s *CardListSort) GetSQLField() string {
return SupportedSortFields[strings.ToLower(s.Field)]
}
func (s *CardListSort) GetSQLOrder() string {
return strings.ToUpper(s.Order)
}