243 lines
6.8 KiB
Go
243 lines
6.8 KiB
Go
package cards
|
|
|
|
import (
|
|
"github.com/go-playground/validator/v10"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type MetricType string
|
|
type MetricOfTimeseries string
|
|
type MetricOfTable string
|
|
|
|
const (
|
|
MetricTypeTimeseries MetricType = "TIMESERIES"
|
|
MetricTypeTable MetricType = "TABLE"
|
|
|
|
MetricOfTimeseriesSessionCount MetricOfTimeseries = "SESSION_COUNT"
|
|
MetricOfTimeseriesUserCount MetricOfTimeseries = "USER_COUNT"
|
|
|
|
MetricOfTableVisitedURL MetricOfTable = "VISITED_URL"
|
|
MetricOfTableIssues MetricOfTable = "ISSUES"
|
|
MetricOfTableUserCountry MetricOfTable = "USER_COUNTRY"
|
|
MetricOfTableUserDevice MetricOfTable = "USER_DEVICE"
|
|
MetricOfTableUserBrowser MetricOfTable = "USER_BROWSER"
|
|
)
|
|
|
|
// 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 {
|
|
EventsOrder string `json:"eventsOrder" 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)
|
|
}
|
|
|
|
// ---
|
|
|
|
/*
|
|
class IssueType(str, Enum):
|
|
|
|
CLICK_RAGE = 'click_rage'
|
|
DEAD_CLICK = 'dead_click'
|
|
EXCESSIVE_SCROLLING = 'excessive_scrolling'
|
|
BAD_REQUEST = 'bad_request'
|
|
MISSING_RESOURCE = 'missing_resource'
|
|
MEMORY = 'memory'
|
|
CPU = 'cpu'
|
|
SLOW_RESOURCE = 'slow_resource'
|
|
SLOW_PAGE_LOAD = 'slow_page_load'
|
|
CRASH = 'crash'
|
|
CUSTOM = 'custom'
|
|
JS_EXCEPTION = 'js_exception'
|
|
MOUSE_THRASHING = 'mouse_thrashing'
|
|
# IOS
|
|
TAP_RAGE = 'tap_rage'
|
|
*/
|
|
type IssueType string
|
|
type ChartData struct {
|
|
StartTs uint64 `json:"startTs"`
|
|
EndTs uint64 `json:"endTs"`
|
|
Density uint64 `json:"density"`
|
|
Filters []FilterItem `json:"filter"`
|
|
MetricOf string `json:"metricOf"`
|
|
MetricValue []IssueType `json:"metricValue"`
|
|
}
|