Shivanshu Raj Shrivastava b36d2ec4c6
feat: adds trace funnels (#7315)
* feat: trace funnels

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
2025-03-23 22:35:30 +05:30

146 lines
3.4 KiB
Go

package traceFunnels
import (
"fmt"
"sort"
"sync"
"github.com/google/uuid"
)
type FunnelStore struct {
sync.RWMutex
funnels map[string]*Funnel
}
func (s *FunnelStore) CreateFunnel(name, userID, orgID string, timestamp int64) (*Funnel, error) {
s.Lock()
defer s.Unlock()
for _, existingFunnel := range s.funnels {
if existingFunnel.Name == name && existingFunnel.CreatedBy == userID {
return nil, fmt.Errorf("funnel with name '%s' already exists for user '%s'", name, userID)
}
}
if timestamp == 0 {
return nil, fmt.Errorf("timestamp is required")
}
funnel := &Funnel{
ID: uuid.New().String(),
Name: name,
CreatedAt: timestamp * 1000000, // Convert milliseconds to nanoseconds for internal storage
CreatedBy: userID,
OrgID: orgID,
Steps: make([]FunnelStep, 0),
}
s.funnels[funnel.ID] = funnel
return funnel, nil
}
func (s *FunnelStore) GetFunnel(id string) (*Funnel, error) {
s.RLock()
defer s.RUnlock()
funnel, ok := s.funnels[id]
if !ok {
return nil, fmt.Errorf("funnel not found")
}
return funnel, nil
}
func (s *FunnelStore) ListFunnels() []*Funnel {
s.RLock()
defer s.RUnlock()
funnels := make([]*Funnel, 0, len(s.funnels))
for _, funnel := range s.funnels {
funnels = append(funnels, funnel)
}
return funnels
}
func (s *FunnelStore) UpdateFunnelSteps(id string, steps []FunnelStep, updatedBy string, updatedAt int64) error {
s.Lock()
defer s.Unlock()
funnel, ok := s.funnels[id]
if !ok {
return fmt.Errorf("funnel with ID %s not found", id)
}
funnel.Steps = steps
funnel.UpdatedAt = updatedAt * 1000000
funnel.UpdatedBy = updatedBy
return nil
}
// DeleteFunnel removes a funnel from the in-memory store
func (s *FunnelStore) DeleteFunnel(id string) error {
s.Lock()
defer s.Unlock()
if _, ok := s.funnels[id]; !ok {
return fmt.Errorf("funnel with ID %s not found", id)
}
delete(s.funnels, id)
return nil
}
// ValidateFunnelSteps validates funnel steps and ensures they have unique and correct order
// Rules: At least 2 steps, max 3 steps, orders must be unique and include 1 and 2
func ValidateFunnelSteps(steps []FunnelStep) error {
if len(steps) < 2 {
return fmt.Errorf("at least 2 funnel steps are required")
}
if len(steps) > 3 {
return fmt.Errorf("maximum 3 funnel steps are allowed")
}
orderMap := make(map[int64]bool)
for _, step := range steps {
if orderMap[step.StepOrder] {
return fmt.Errorf("duplicate step order: %d", step.StepOrder)
}
orderMap[step.StepOrder] = true
if step.StepOrder < 1 || step.StepOrder > 3 {
return fmt.Errorf("step order must be between 1 and 3, got: %d", step.StepOrder)
}
}
if !orderMap[1] || !orderMap[2] {
return fmt.Errorf("funnel steps with orders 1 and 2 are mandatory")
}
return nil
}
// NormalizeFunnelSteps ensures steps have sequential orders starting from 1
// This sorts steps by order and then reassigns orders to be sequential
func NormalizeFunnelSteps(steps []FunnelStep) []FunnelStep {
// Create a copy of the input slice
sortedSteps := make([]FunnelStep, len(steps))
copy(sortedSteps, steps)
// Sort using Go's built-in sort.Slice function
sort.Slice(sortedSteps, func(i, j int) bool {
return sortedSteps[i].StepOrder < sortedSteps[j].StepOrder
})
// Normalize orders to be sequential starting from 1
for i := 0; i < len(sortedSteps); i++ {
sortedSteps[i].StepOrder = int64(i + 1)
}
return sortedSteps
}