fix: optimize funnel creation by combining insert and update operations

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
This commit is contained in:
Shivanshu Raj Shrivastava 2025-04-29 18:25:03 +05:30
parent 7a7428d73e
commit f8341e8958
No known key found for this signature in database
GPG Key ID: D34D26C62AC3E9AE
5 changed files with 19 additions and 62 deletions

View File

@ -1,7 +1,6 @@
package impltracefunnel package impltracefunnel
import ( import (
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"time" "time"
@ -21,25 +20,6 @@ func NewHandler(module tracefunnel.Module) tracefunnel.Handler {
return &handler{module: module} return &handler{module: module}
} }
// Helper function to check for duplicate funnel names
func (handler *handler) checkDuplicateName(ctx context.Context, orgID string, name string, excludeID string) error {
funnels, err := handler.module.List(ctx, orgID)
if err != nil {
return errors.Newf(errors.TypeInvalidInput,
errors.CodeInvalidInput,
"failed to list funnels: %v", err)
}
for _, f := range funnels {
if f.ID.String() != excludeID && f.Name == name {
return errors.Newf(errors.TypeInvalidInput,
errors.CodeInvalidInput,
"a funnel with name '%s' already exists in this organization", name)
}
}
return nil
}
func (handler *handler) New(rw http.ResponseWriter, r *http.Request) { func (handler *handler) New(rw http.ResponseWriter, r *http.Request) {
var req tf.FunnelRequest var req tf.FunnelRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@ -53,16 +33,11 @@ func (handler *handler) New(rw http.ResponseWriter, r *http.Request) {
return return
} }
if err := handler.checkDuplicateName(r.Context(), claims.OrgID, req.Name, ""); err != nil {
render.Error(rw, err)
return
}
funnel, err := handler.module.Create(r.Context(), req.Timestamp, req.Name, claims.UserID, claims.OrgID) funnel, err := handler.module.Create(r.Context(), req.Timestamp, req.Name, claims.UserID, claims.OrgID)
if err != nil { if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, render.Error(rw, errors.Newf(errors.TypeInvalidInput,
errors.CodeInvalidInput, errors.CodeInvalidInput,
"failed to create funnel")) "failed to create funnel: %v", err))
return return
} }
@ -97,13 +72,6 @@ func (handler *handler) UpdateSteps(rw http.ResponseWriter, r *http.Request) {
return return
} }
if req.Name != "" && req.Name != funnel.Name {
if err := handler.checkDuplicateName(r.Context(), claims.OrgID, req.Name, funnel.ID.String()); err != nil {
render.Error(rw, err)
return
}
}
steps, err := tracefunnel.ProcessFunnelSteps(req.Steps) steps, err := tracefunnel.ProcessFunnelSteps(req.Steps)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
@ -170,13 +138,6 @@ func (handler *handler) UpdateFunnel(rw http.ResponseWriter, r *http.Request) {
return return
} }
if req.Name != "" && req.Name != funnel.Name {
if err := handler.checkDuplicateName(r.Context(), claims.OrgID, req.Name, funnel.ID.String()); err != nil {
render.Error(rw, err)
return
}
}
funnel.UpdatedAt = updatedAt funnel.UpdatedAt = updatedAt
funnel.UpdatedBy = claims.UserID funnel.UpdatedBy = claims.UserID
@ -300,9 +261,7 @@ func (handler *handler) Save(rw http.ResponseWriter, r *http.Request) {
} }
funnel.UpdatedAt = updatedAt funnel.UpdatedAt = updatedAt
if req.UserID != "" {
funnel.UpdatedBy = claims.UserID funnel.UpdatedBy = claims.UserID
}
funnel.Description = req.Description funnel.Description = req.Description
if err := handler.module.Save(r.Context(), funnel, funnel.UpdatedBy, claims.OrgID); err != nil { if err := handler.module.Save(r.Context(), funnel, funnel.UpdatedBy, claims.OrgID); err != nil {

View File

@ -12,10 +12,10 @@ import (
) )
type module struct { type module struct {
store traceFunnels.TraceFunnelStore store traceFunnels.FunnelStore
} }
func NewModule(store traceFunnels.TraceFunnelStore) tracefunnel.Module { func NewModule(store traceFunnels.FunnelStore) tracefunnel.Module {
return &module{ return &module{
store: store, store: store,
} }

View File

@ -3,6 +3,7 @@ package impltracefunnel
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
@ -14,7 +15,7 @@ type store struct {
sqlstore sqlstore.SQLStore sqlstore sqlstore.SQLStore
} }
func NewStore(sqlstore sqlstore.SQLStore) traceFunnels.TraceFunnelStore { func NewStore(sqlstore sqlstore.SQLStore) traceFunnels.FunnelStore {
return &store{sqlstore: sqlstore} return &store{sqlstore: sqlstore}
} }
@ -30,6 +31,11 @@ func (store *store) Create(ctx context.Context, funnel *traceFunnels.Funnel) err
funnel.UpdatedAt = time.Now() funnel.UpdatedAt = time.Now()
} }
// Set created_by if CreatedByUser is present
if funnel.CreatedByUser != nil {
funnel.CreatedBy = funnel.CreatedByUser.ID
}
_, err := store. _, err := store.
sqlstore. sqlstore.
BunDB(). BunDB().
@ -37,20 +43,12 @@ func (store *store) Create(ctx context.Context, funnel *traceFunnels.Funnel) err
Model(funnel). Model(funnel).
Exec(ctx) Exec(ctx)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "idx_trace_funnel_org_id_name") {
return fmt.Errorf("a funnel with name '%s' already exists in this organization", funnel.Name)
}
return fmt.Errorf("failed to create funnel: %v", err) return fmt.Errorf("failed to create funnel: %v", err)
} }
if funnel.CreatedByUser != nil {
_, err = store.sqlstore.BunDB().NewUpdate().
Model(funnel).
Set("created_by = ?", funnel.CreatedByUser.ID).
Where("id = ?", funnel.ID).
Exec(ctx)
if err != nil {
return fmt.Errorf("failed to update funnel user relationship: %v", err)
}
}
return nil return nil
} }
@ -83,6 +81,9 @@ func (store *store) Update(ctx context.Context, funnel *traceFunnels.Funnel) err
WherePK(). WherePK().
Exec(ctx) Exec(ctx)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "idx_trace_funnel_org_id_name") {
return fmt.Errorf("a funnel with name '%s' already exists in this organization", funnel.Name)
}
return fmt.Errorf("failed to update funnel: %v", err) return fmt.Errorf("failed to update funnel: %v", err)
} }
return nil return nil

View File

@ -52,19 +52,16 @@ func ValidateFunnelSteps(steps []tracefunnel.FunnelStep) error {
// Returns a new slice with normalized step orders, leaving the input slice unchanged. // Returns a new slice with normalized step orders, leaving the input slice unchanged.
func NormalizeFunnelSteps(steps []tracefunnel.FunnelStep) []tracefunnel.FunnelStep { func NormalizeFunnelSteps(steps []tracefunnel.FunnelStep) []tracefunnel.FunnelStep {
if len(steps) == 0 { if len(steps) == 0 {
return nil return []tracefunnel.FunnelStep{}
} }
// Create a copy of the input slice
newSteps := make([]tracefunnel.FunnelStep, len(steps)) newSteps := make([]tracefunnel.FunnelStep, len(steps))
copy(newSteps, steps) copy(newSteps, steps)
// Sort the copy
sort.Slice(newSteps, func(i, j int) bool { sort.Slice(newSteps, func(i, j int) bool {
return newSteps[i].Order < newSteps[j].Order return newSteps[i].Order < newSteps[j].Order
}) })
// Update orders in the copy
for i := range newSteps { for i := range newSteps {
newSteps[i].Order = int64(i + 1) newSteps[i].Order = int64(i + 1)
} }

View File

@ -6,7 +6,7 @@ import (
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
) )
type TraceFunnelStore interface { type FunnelStore interface {
Create(context.Context, *Funnel) error Create(context.Context, *Funnel) error
Get(context.Context, valuer.UUID) (*Funnel, error) Get(context.Context, valuer.UUID) (*Funnel, error)
List(context.Context, valuer.UUID) ([]*Funnel, error) List(context.Context, valuer.UUID) ([]*Funnel, error)